1<?php 2 3namespace dokuwiki\Form; 4 5/** 6 * Class DropdownElement 7 * 8 * Represents a HTML select. Please not that prefilling with input data only works for single values. 9 * 10 * @package dokuwiki\Form 11 */ 12class DropdownElement extends InputElement 13{ 14 /** @var array OptGroup[] */ 15 protected $optGroups = []; 16 17 /** @var string[] the currently set values */ 18 protected $values = []; 19 20 /** 21 * @param string $name The name of this form element 22 * @param array $options The available options 23 * @param string $label The label text for this element (will be autoescaped) 24 */ 25 public function __construct($name, $options, $label = '') 26 { 27 parent::__construct('dropdown', $name, $label); 28 $this->rmattr('type'); 29 $this->optGroups[''] = new OptGroup(null, $options); 30 $this->val(''); 31 } 32 33 /** 34 * Add an `<optgroup>` and respective options 35 * 36 * @param string $label 37 * @param array $options 38 * @return OptGroup a reference to the added optgroup 39 * @throws \InvalidArgumentException 40 */ 41 public function addOptGroup($label, $options) 42 { 43 if (empty($label)) { 44 throw new \InvalidArgumentException(hsc('<optgroup> must have a label!')); 45 } 46 $this->optGroups[$label] = new OptGroup($label, $options); 47 return end($this->optGroups); 48 } 49 50 /** 51 * Set or get the optgroups of an Dropdown-Element. 52 * 53 * optgroups have to be given as associative array 54 * * the key being the label of the group 55 * * the value being an array of options as defined in @param null|array $optGroups 56 * @return OptGroup[]|DropdownElement 57 * @see OptGroup::options() 58 * 59 */ 60 public function optGroups($optGroups = null) 61 { 62 if ($optGroups === null) { 63 return $this->optGroups; 64 } 65 if (!is_array($optGroups)) { 66 throw new \InvalidArgumentException(hsc('Argument must be an associative array of label => [options]!')); 67 } 68 $this->optGroups = []; 69 foreach ($optGroups as $label => $options) { 70 $this->addOptGroup($label, $options); 71 } 72 return $this; 73 } 74 75 /** 76 * Get or set the options of the Dropdown 77 * 78 * Options can be given as associative array (value => label) or as an 79 * indexd array (label = value) or as an array of arrays. In the latter 80 * case an element has to look as follows: 81 * option-value => array ( 82 * 'label' => option-label, 83 * 'attrs' => array ( 84 * attr-key => attr-value, ... 85 * ) 86 * ) 87 * 88 * @param null|array $options 89 * @return $this|array 90 */ 91 public function options($options = null) 92 { 93 if ($options === null) { 94 return $this->optGroups['']->options(); 95 } 96 $this->optGroups[''] = new OptGroup(null, $options); 97 return $this; 98 } 99 100 /** 101 * Get or set the current value 102 * 103 * When setting a value that is not defined in the options, the value is ignored 104 * and the first option's value is selected instead 105 * 106 * @param null|string|string[] $value The value to set 107 * @return $this|string|string[] 108 */ 109 public function val($value = null) 110 { 111 // getter 112 if ($value === null) { 113 if (isset($this->attributes['multiple'])) { 114 return $this->values; 115 } else { 116 return $this->values[0]; 117 } 118 } 119 120 // setter 121 $this->values = $this->setValuesInOptGroups((array) $value); 122 if (!$this->values) { 123 // unknown value set, select first option instead 124 $this->values = $this->setValuesInOptGroups((array) $this->getFirstOptionKey()); 125 } 126 127 return $this; 128 } 129 130 /** 131 * Returns the first option's key 132 * 133 * @return string 134 */ 135 protected function getFirstOptionKey() 136 { 137 $options = $this->options(); 138 if (!empty($options)) { 139 $keys = array_keys($options); 140 return (string)array_shift($keys); 141 } 142 foreach ($this->optGroups as $optGroup) { 143 $options = $optGroup->options(); 144 if (!empty($options)) { 145 $keys = array_keys($options); 146 return (string)array_shift($keys); 147 } 148 } 149 150 return ''; // should not happen 151 } 152 153 /** 154 * Set the value in the OptGroups, including the optgroup for the options without optgroup. 155 * 156 * @param string[] $values The values to be set 157 * @return string[] The values actually set 158 */ 159 protected function setValuesInOptGroups($values) 160 { 161 $valueset = []; 162 163 /** @var OptGroup $optGroup */ 164 foreach ($this->optGroups as $optGroup) { 165 $found = $optGroup->storeValues($values); 166 $values = array_diff($values, $found); 167 $valueset = array_merge($valueset, $found); 168 } 169 170 return $valueset; 171 } 172 173 /** 174 * Create the HTML for the select it self 175 * 176 * @return string 177 */ 178 protected function mainElementHTML() 179 { 180 $attr = $this->attrs(); 181 if (isset($attr['multiple'])) { 182 // use array notation when multiple values are allowed 183 $attr['name'] .= '[]'; 184 } elseif ($this->useInput) { 185 // prefilling is only supported for non-multi fields 186 $this->prefillInput(); 187 } 188 189 $html = '<select ' . buildAttributes($attr) . '>'; 190 $html = array_reduce( 191 $this->optGroups, 192 static fn($html, OptGroup $optGroup) => $html . $optGroup->toHTML(), 193 $html 194 ); 195 $html .= '</select>'; 196 197 return $html; 198 } 199} 200