1<?php
2
3namespace dokuwiki\plugin\data\Form;
4
5use dokuwiki\Form\InputElement;
6use dokuwiki\Form\OptGroup;
7
8/**
9 * Overrides some methods in parent in order to add not yet supported
10 * multivalue capabilities.
11 */
12class DropdownElement extends \dokuwiki\Form\DropdownElement
13{
14    /** @var string[] the currently set values */
15    protected $values = [];
16
17    /** @var \dokuwiki\plugin\data\Form\OptGroup[] */
18    protected $optGroups = [];
19
20
21    /**
22     * Override the parent constructor because it instantiates an OptGroup
23     * which does not handle multivalues
24     *
25     * @param string $name
26     * @param array $options
27     * @param string $label
28     */
29    public function __construct($name, $options, $label = '')
30    {
31        InputElement::__construct('dropdown', $name, $label);
32        $this->rmattr('type');
33        $this->optGroups[''] = new \dokuwiki\plugin\data\Form\OptGroup(null, $options);
34        $this->val('');
35    }
36
37    /**
38     * Adds multivalue capabilities
39     *
40     * @param array $value
41     * @return DropdownElement|string|array
42     */
43    public function val($value = null)
44    {
45        // getter
46        if ($value === null) {
47            if (isset($this->attributes['multiple'])) {
48                return $this->values;
49            } else {
50                return $this->values[0];
51            }
52        }
53
54        // setter
55        $this->values = $this->setValuesInOptGroups((array)$value);
56        if (!$this->values) {
57            // unknown value set, select first option instead
58            $this->values = $this->setValuesInOptGroups((array)$this->getFirstOptionKey());
59        }
60
61        return $this;
62    }
63
64    /**
65     * Skips over parent's \InvalidArgumentException thrown for 'multiple'
66     *
67     * @param $name
68     * @param $value
69     * @return DropdownElement|string
70     */
71    public function attr($name, $value = null)
72    {
73        return InputElement::attr($name, $value);
74    }
75
76    /**
77     * Returns the first option's key
78     *
79     * @return string
80     */
81    protected function getFirstOptionKey()
82    {
83        $options = $this->options();
84        if (!empty($options)) {
85            $keys = array_keys($options);
86            return (string)array_shift($keys);
87        }
88        foreach ($this->optGroups as $optGroup) {
89            $options = $optGroup->options();
90            if (!empty($options)) {
91                $keys = array_keys($options);
92                return (string)array_shift($keys);
93            }
94        }
95
96        return ''; // should not happen
97    }
98
99    /**
100     * Set the value in the OptGroups, including the optgroup for the options without optgroup.
101     *
102     * @param string[] $values The values to be set
103     * @return string[] The values actually set
104     */
105    protected function setValuesInOptGroups($values)
106    {
107        $valueset = [];
108
109        /** @var \dokuwiki\plugin\data\Form\OptGroup $optGroup */
110        foreach ($this->optGroups as $optGroup) {
111            $found = $optGroup->storeValues($values);
112            $values = array_diff($values, $found);
113            $valueset = array_merge($valueset, $found);
114        }
115
116        return $valueset;
117    }
118
119    /**
120     * @inheritDoc
121     */
122    protected function mainElementHTML()
123    {
124        $attr = $this->attrs();
125        if (isset($attr['multiple'])) {
126            // use array notation when multiple values are allowed
127            $attr['name'] .= '[]';
128        } elseif ($this->useInput) {
129            // prefilling is only supported for non-multi fields
130            $this->prefillInput();
131        }
132
133        unset($attr['selected']);
134
135        $html = '<select ' . buildAttributes($attr) . '>';
136        $html = array_reduce(
137            $this->optGroups,
138            function ($html, OptGroup $optGroup) {
139                return $html . $optGroup->toHTML();
140            },
141            $html
142        );
143        $html .= '</select>';
144
145        return $html;
146    }
147}
148