1<?php
2/**
3 * Base class for HTML_QuickForm2 groups
4 *
5 * PHP version 5
6 *
7 * LICENSE
8 *
9 * This source file is subject to BSD 3-Clause License that is bundled
10 * with this package in the file LICENSE and available at the URL
11 * https://raw.githubusercontent.com/pear/HTML_QuickForm2/trunk/docs/LICENSE
12 *
13 * @category  HTML
14 * @package   HTML_QuickForm2
15 * @author    Alexey Borzov <avb@php.net>
16 * @author    Bertrand Mansion <golgote@mamasam.com>
17 * @copyright 2006-2021 Alexey Borzov <avb@php.net>, Bertrand Mansion <golgote@mamasam.com>
18 * @license   https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
19 * @link      https://pear.php.net/package/HTML_QuickForm2
20 */
21
22/**
23 * Base class for all HTML_QuickForm2 containers
24 */
25require_once 'HTML/QuickForm2/Container.php';
26
27/**
28 * Base class for QuickForm2 groups of elements
29 *
30 * @category HTML
31 * @package  HTML_QuickForm2
32 * @author   Alexey Borzov <avb@php.net>
33 * @author   Bertrand Mansion <golgote@mamasam.com>
34 * @license  https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
35 * @version  Release: 2.2.2
36 * @link     https://pear.php.net/package/HTML_QuickForm2
37 */
38class HTML_QuickForm2_Container_Group extends HTML_QuickForm2_Container
39{
40   /**
41    * Group name
42    * If set, group name will be used as prefix for contained
43    * element names, like groupname[elementname].
44    * @var string
45    */
46    protected $name;
47
48   /**
49    * Previous group name
50    * Stores the previous group name when the group name is changed.
51    * Used to restore children names if necessary.
52    * @var string
53    */
54    protected $previousName;
55
56    public function getType()
57    {
58        return 'group';
59    }
60
61    protected function prependsName()
62    {
63        return strlen($this->name) > 0;
64    }
65
66    protected function getChildValues($filtered = false)
67    {
68        $value = parent::getChildValues($filtered);
69        if (!$this->prependsName()) {
70            return $value;
71
72        } elseif (!strpos($this->getName(), '[')) {
73            return isset($value[$this->getName()])? $value[$this->getName()]: null;
74
75        } else {
76            $tokens   =  explode('[', str_replace(']', '', $this->getName()));
77            $valueAry =& $value;
78            do {
79                $token = array_shift($tokens);
80                if (!isset($valueAry[$token])) {
81                    return null;
82                }
83                $valueAry =& $valueAry[$token];
84            } while ($tokens);
85            return $valueAry;
86        }
87    }
88
89    public function setValue($value)
90    {
91        // Prepare a mapper for element names as array
92        $prefixLength = $this->prependsName() ? substr_count($this->getName(), '[') + 1 : 0;
93        $nameParts = $groupValues = [];
94
95        /* @var $child HTML_QuickForm2_Node */
96        foreach ($this as $i => $child) {
97            $tokens = explode('[', str_replace(']', '', $child->getName()));
98            if ($prefixLength) {
99                $tokens = array_slice($tokens, $prefixLength);
100            }
101            $nameParts[] = $tokens;
102            if ($child instanceof self) {
103                $groupValues[$i] = [];
104            }
105        }
106
107        // Iterate over values to find corresponding element
108
109        $index = 0;
110
111        foreach ((array)$value as $k => $v) {
112            foreach ($nameParts as $i => $tokens) {
113                $val = [$k => $v];
114                do {
115                    $token = array_shift($tokens);
116                    $numeric = false;
117                    if ($token == "") {
118                        // special case for a group of checkboxes
119                        if (empty($tokens) && is_array($val)
120                            && $this->elements[$i] instanceof HTML_QuickForm2_Element_InputCheckbox
121                        ) {
122                            if (in_array($this->elements[$i]->getAttribute('value'),
123                                array_map('strval', $val), true)
124                            ) {
125                                $this->elements[$i]->setAttribute('checked');
126                                // don't want to remove 'checked' on next iteration
127                                unset($nameParts[$i]);
128                            } else {
129                                $this->elements[$i]->removeAttribute('checked');
130                            }
131                            continue 2;
132                        }
133                        // Deal with numeric indexes in values
134                        $token = $index;
135                        $numeric = true;
136                    }
137                    if (!is_array($val) || !isset($val[$token])) {
138                        // Not found, skip next iterations
139                        continue 2;
140
141                    } else {
142                        // Found a value
143                        $val = $val[$token];
144                        if ($numeric) {
145                            $index += 1;
146                        }
147                    }
148
149                } while (!empty($tokens));
150
151                // Found a value corresponding to element name
152                $child = $this->elements[$i];
153                if ($child instanceof self) {
154                    $groupValues[$i] += (array)$val;
155                } else {
156                    $child->setValue($val);
157                    // Speed up next iterations
158                    unset($nameParts[$i]);
159                }
160                if (!($child instanceof HTML_QuickForm2_Element_InputRadio)) {
161                    break;
162                }
163            }
164        }
165        foreach (array_keys($nameParts) as $i) {
166            $this->elements[$i]->setValue(isset($groupValues[$i]) ? $groupValues[$i] : null);
167        }
168
169        return $this;
170    }
171
172
173    public function getName()
174    {
175        return $this->name;
176    }
177
178    public function setName($name)
179    {
180        $this->previousName = $this->name;
181        $this->name = $name;
182        foreach ($this as $child) {
183            $this->renameChild($child);
184        }
185        return $this;
186    }
187
188   /**
189    * Prepends group's name to contained element's name
190    *
191    * Used when adding an element to the group or changing group's name
192    *
193    * @param HTML_QuickForm2_Node $element
194    *
195    * @return HTML_QuickForm2_Node
196    */
197    protected function renameChild(HTML_QuickForm2_Node $element)
198    {
199        $tokens = explode('[', str_replace(']', '', $element->getName()));
200        // Child has already been renamed by its group before
201        if ($this === $element->getContainer() && strlen($this->previousName)) {
202            $gtokens = explode('[', str_replace(']', '', $this->previousName));
203            if ($gtokens === array_slice($tokens, 0, count($gtokens))) {
204                array_splice($tokens, 0, count($gtokens));
205            }
206        }
207
208        if (strlen($this->name)) {
209            $element->setName($this->name . '[' . implode('][', $tokens) . ']');
210        } elseif (strlen($this->previousName)) {
211            $elname = array_shift($tokens);
212            foreach ($tokens as $token) {
213                $elname .= '[' . $token . ']';
214            }
215            $element->setName($elname);
216        }
217
218        return $element;
219    }
220
221   /**
222    * Appends an element to the container
223    *
224    * If the element was previously added to the container or to another
225    * container, it is first removed there.
226    *
227    * @param HTML_QuickForm2_Node $element Element to add
228    *
229    * @return   HTML_QuickForm2_Node     Added element
230    * @throws   HTML_QuickForm2_InvalidArgumentException
231    */
232    public function appendChild(HTML_QuickForm2_Node $element)
233    {
234        if (null !== ($container = $element->getContainer())) {
235            $container->removeChild($element);
236        }
237        // Element can be renamed only after being removed from container
238        $this->renameChild($element);
239
240        $element->setContainer($this);
241        $this->elements[] = $element;
242        return $element;
243    }
244
245   /**
246    * Removes the element from this container
247    *
248    * If the reference object is not given, the element will be appended.
249    *
250    * @param HTML_QuickForm2_Node $element Element to remove
251    *
252    * @return   HTML_QuickForm2_Node     Removed object
253    */
254    public function removeChild(HTML_QuickForm2_Node $element)
255    {
256        $element = parent::removeChild($element);
257        if ($this->prependsName()) {
258            $name = preg_replace(
259                '/^' . preg_quote($this->getName(), '/') . '\[([^\]]*)\]/',
260                '\1', $element->getName()
261            );
262            $element->setName($name);
263        }
264        return $element;
265    }
266
267   /**
268    * Inserts an element in the container
269    *
270    * If the reference object is not given, the element will be appended.
271    *
272    * @param HTML_QuickForm2_Node $element   Element to insert
273    * @param HTML_QuickForm2_Node $reference Reference to insert before
274    *
275    * @return   HTML_QuickForm2_Node     Inserted element
276    */
277    public function insertBefore(HTML_QuickForm2_Node $element, HTML_QuickForm2_Node $reference = null)
278    {
279        if (null === $reference) {
280            return $this->appendChild($element);
281        }
282        return parent::insertBefore($this->renameChild($element), $reference);
283    }
284
285   /**
286    * Sets string(s) to separate grouped elements
287    *
288    * @param string|array $separator Use a string for one separator, array for
289    *                                alternating separators
290    *
291    * @return $this
292    */
293    public function setSeparator($separator)
294    {
295        $this->data['separator'] = $separator;
296        return $this;
297    }
298
299   /**
300    * Returns string(s) to separate grouped elements
301    *
302    * @return   string|array    Separator, null if not set
303    */
304    public function getSeparator()
305    {
306        return isset($this->data['separator'])? $this->data['separator']: null;
307    }
308
309   /**
310    * Renders the group using the given renderer
311    *
312    * @param HTML_QuickForm2_Renderer $renderer
313    *
314    * @return   HTML_QuickForm2_Renderer
315    */
316    public function render(HTML_QuickForm2_Renderer $renderer)
317    {
318        $renderer->startGroup($this);
319        foreach ($this as $element) {
320            $element->render($renderer);
321        }
322        $this->renderClientRules($renderer->getJavascriptBuilder());
323        $renderer->finishGroup($this);
324        return $renderer;
325    }
326
327    public function __toString()
328    {
329        HTML_QuickForm2_Loader::loadClass('HTML_QuickForm2_Renderer');
330
331        $renderer = $this->render(
332            HTML_QuickForm2_Renderer::factory('default')
333                ->setTemplateForId($this->getId(), '{content}')
334        );
335        return $renderer->__toString()
336               . $renderer->getJavascriptBuilder()->getSetupCode(null, true);
337    }
338}
339?>