1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\Form\View\Helper; 11 12use Zend\Form\Element\Hidden; 13use Zend\Form\ElementInterface; 14use Zend\Form\Element\Select as SelectElement; 15use Zend\Form\Exception; 16use Zend\Stdlib\ArrayUtils; 17 18class FormSelect extends AbstractHelper 19{ 20 /** 21 * Attributes valid for the current tag 22 * 23 * Will vary based on whether a select, option, or optgroup is being rendered 24 * 25 * @var array 26 */ 27 protected $validTagAttributes; 28 29 /** 30 * Attributes valid for select 31 * 32 * @var array 33 */ 34 protected $validSelectAttributes = array( 35 'name' => true, 36 'autocomplete' => true, 37 'autofocus' => true, 38 'disabled' => true, 39 'form' => true, 40 'multiple' => true, 41 'required' => true, 42 'size' => true 43 ); 44 45 /** 46 * Attributes valid for options 47 * 48 * @var array 49 */ 50 protected $validOptionAttributes = array( 51 'disabled' => true, 52 'selected' => true, 53 'label' => true, 54 'value' => true, 55 ); 56 57 /** 58 * Attributes valid for option groups 59 * 60 * @var array 61 */ 62 protected $validOptgroupAttributes = array( 63 'disabled' => true, 64 'label' => true, 65 ); 66 67 protected $translatableAttributes = array( 68 'label' => true, 69 ); 70 71 /** 72 * @var FormHidden|null 73 */ 74 protected $formHiddenHelper; 75 76 /** 77 * Invoke helper as functor 78 * 79 * Proxies to {@link render()}. 80 * 81 * @param ElementInterface|null $element 82 * @return string|FormSelect 83 */ 84 public function __invoke(ElementInterface $element = null) 85 { 86 if (!$element) { 87 return $this; 88 } 89 90 return $this->render($element); 91 } 92 93 /** 94 * Render a form <select> element from the provided $element 95 * 96 * @param ElementInterface $element 97 * @throws Exception\InvalidArgumentException 98 * @throws Exception\DomainException 99 * @return string 100 */ 101 public function render(ElementInterface $element) 102 { 103 if (!$element instanceof SelectElement) { 104 throw new Exception\InvalidArgumentException(sprintf( 105 '%s requires that the element is of type Zend\Form\Element\Select', 106 __METHOD__ 107 )); 108 } 109 110 $name = $element->getName(); 111 if (empty($name) && $name !== 0) { 112 throw new Exception\DomainException(sprintf( 113 '%s requires that the element has an assigned name; none discovered', 114 __METHOD__ 115 )); 116 } 117 118 $options = $element->getValueOptions(); 119 120 if (($emptyOption = $element->getEmptyOption()) !== null) { 121 $options = array('' => $emptyOption) + $options; 122 } 123 124 $attributes = $element->getAttributes(); 125 $value = $this->validateMultiValue($element->getValue(), $attributes); 126 127 $attributes['name'] = $name; 128 if (array_key_exists('multiple', $attributes) && $attributes['multiple']) { 129 $attributes['name'] .= '[]'; 130 } 131 $this->validTagAttributes = $this->validSelectAttributes; 132 133 $rendered = sprintf( 134 '<select %s>%s</select>', 135 $this->createAttributesString($attributes), 136 $this->renderOptions($options, $value) 137 ); 138 139 // Render hidden element 140 $useHiddenElement = method_exists($element, 'useHiddenElement') 141 && method_exists($element, 'getUnselectedValue') 142 && $element->useHiddenElement(); 143 144 if ($useHiddenElement) { 145 $rendered = $this->renderHiddenElement($element) . $rendered; 146 } 147 148 return $rendered; 149 } 150 151 /** 152 * Render an array of options 153 * 154 * Individual options should be of the form: 155 * 156 * <code> 157 * array( 158 * 'value' => 'value', 159 * 'label' => 'label', 160 * 'disabled' => $booleanFlag, 161 * 'selected' => $booleanFlag, 162 * ) 163 * </code> 164 * 165 * @param array $options 166 * @param array $selectedOptions Option values that should be marked as selected 167 * @return string 168 */ 169 public function renderOptions(array $options, array $selectedOptions = array()) 170 { 171 $template = '<option %s>%s</option>'; 172 $optionStrings = array(); 173 $escapeHtml = $this->getEscapeHtmlHelper(); 174 175 foreach ($options as $key => $optionSpec) { 176 $value = ''; 177 $label = ''; 178 $selected = false; 179 $disabled = false; 180 181 if (is_scalar($optionSpec)) { 182 $optionSpec = array( 183 'label' => $optionSpec, 184 'value' => $key 185 ); 186 } 187 188 if (isset($optionSpec['options']) && is_array($optionSpec['options'])) { 189 $optionStrings[] = $this->renderOptgroup($optionSpec, $selectedOptions); 190 continue; 191 } 192 193 if (isset($optionSpec['value'])) { 194 $value = $optionSpec['value']; 195 } 196 if (isset($optionSpec['label'])) { 197 $label = $optionSpec['label']; 198 } 199 if (isset($optionSpec['selected'])) { 200 $selected = $optionSpec['selected']; 201 } 202 if (isset($optionSpec['disabled'])) { 203 $disabled = $optionSpec['disabled']; 204 } 205 206 if (ArrayUtils::inArray($value, $selectedOptions)) { 207 $selected = true; 208 } 209 210 if (null !== ($translator = $this->getTranslator())) { 211 $label = $translator->translate( 212 $label, 213 $this->getTranslatorTextDomain() 214 ); 215 } 216 217 $attributes = compact('value', 'selected', 'disabled'); 218 219 if (isset($optionSpec['attributes']) && is_array($optionSpec['attributes'])) { 220 $attributes = array_merge($attributes, $optionSpec['attributes']); 221 } 222 223 $this->validTagAttributes = $this->validOptionAttributes; 224 $optionStrings[] = sprintf( 225 $template, 226 $this->createAttributesString($attributes), 227 $escapeHtml($label) 228 ); 229 } 230 231 return implode("\n", $optionStrings); 232 } 233 234 /** 235 * Render an optgroup 236 * 237 * See {@link renderOptions()} for the options specification. Basically, 238 * an optgroup is simply an option that has an additional "options" key 239 * with an array following the specification for renderOptions(). 240 * 241 * @param array $optgroup 242 * @param array $selectedOptions 243 * @return string 244 */ 245 public function renderOptgroup(array $optgroup, array $selectedOptions = array()) 246 { 247 $template = '<optgroup%s>%s</optgroup>'; 248 249 $options = array(); 250 if (isset($optgroup['options']) && is_array($optgroup['options'])) { 251 $options = $optgroup['options']; 252 unset($optgroup['options']); 253 } 254 255 $this->validTagAttributes = $this->validOptgroupAttributes; 256 $attributes = $this->createAttributesString($optgroup); 257 if (!empty($attributes)) { 258 $attributes = ' ' . $attributes; 259 } 260 261 return sprintf( 262 $template, 263 $attributes, 264 $this->renderOptions($options, $selectedOptions) 265 ); 266 } 267 268 /** 269 * Ensure that the value is set appropriately 270 * 271 * If the element's value attribute is an array, but there is no multiple 272 * attribute, or that attribute does not evaluate to true, then we have 273 * a domain issue -- you cannot have multiple options selected unless the 274 * multiple attribute is present and enabled. 275 * 276 * @param mixed $value 277 * @param array $attributes 278 * @return array 279 * @throws Exception\DomainException 280 */ 281 protected function validateMultiValue($value, array $attributes) 282 { 283 if (null === $value) { 284 return array(); 285 } 286 287 if (!is_array($value)) { 288 return (array) $value; 289 } 290 291 if (!isset($attributes['multiple']) || !$attributes['multiple']) { 292 throw new Exception\DomainException(sprintf( 293 '%s does not allow specifying multiple selected values when the element does not have a multiple attribute set to a boolean true', 294 __CLASS__ 295 )); 296 } 297 298 return $value; 299 } 300 301 protected function renderHiddenElement(ElementInterface $element) 302 { 303 $hiddenElement = new Hidden($element->getName()); 304 $hiddenElement->setValue($element->getUnselectedValue()); 305 306 return $this->getFormHiddenHelper()->__invoke($hiddenElement); 307 } 308 309 /** 310 * @return FormHidden 311 */ 312 protected function getFormHiddenHelper() 313 { 314 if (!$this->formHiddenHelper) { 315 if (method_exists($this->view, 'plugin')) { 316 $this->formHiddenHelper = $this->view->plugin('formhidden'); 317 } 318 319 if (!$this->formHiddenHelper instanceof FormHidden) { 320 $this->formHiddenHelper = new FormHidden(); 321 } 322 } 323 324 return $this->formHiddenHelper; 325 } 326} 327