1<?php
2/**
3 * Base class for simple HTML_QuickForm2 containers
4 *
5 * PHP version 5
6 *
7 * LICENSE:
8 *
9 * Copyright (c) 2006-2010, Alexey Borzov <avb@php.net>,
10 *                          Bertrand Mansion <golgote@mamasam.com>
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 *
17 *    * Redistributions of source code must retain the above copyright
18 *      notice, this list of conditions and the following disclaimer.
19 *    * Redistributions in binary form must reproduce the above copyright
20 *      notice, this list of conditions and the following disclaimer in the
21 *      documentation and/or other materials provided with the distribution.
22 *    * The names of the authors may not be used to endorse or promote products
23 *      derived from this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
26 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
27 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
31 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
32 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
33 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 *
37 * @category   HTML
38 * @package    HTML_QuickForm2
39 * @author     Alexey Borzov <avb@php.net>
40 * @author     Bertrand Mansion <golgote@mamasam.com>
41 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
42 * @version    SVN: $Id: Container.php 300722 2010-06-24 10:15:52Z mansion $
43 * @link       http://pear.php.net/package/HTML_QuickForm2
44 */
45
46/**
47 * Base class for all HTML_QuickForm2 elements
48 */
49// require_once 'HTML/QuickForm2/Node.php';
50
51
52/**
53 * Implements a recursive iterator for the container elements
54 *
55 * @category   HTML
56 * @package    HTML_QuickForm2
57 * @author     Alexey Borzov <avb@php.net>
58 * @author     Bertrand Mansion <golgote@mamasam.com>
59 * @version    Release: @package_version@
60 */
61class HTML_QuickForm2_ContainerIterator extends RecursiveArrayIterator implements RecursiveIterator
62{
63    public function __construct(HTML_QuickForm2_Container $container)
64    {
65        parent::__construct($container->getElements());
66    }
67
68    public function hasChildren()
69    {
70        return $this->current() instanceof HTML_QuickForm2_Container;
71    }
72
73    public function getChildren()
74    {
75        return new HTML_QuickForm2_ContainerIterator($this->current());
76    }
77}
78
79/**
80 * Abstract base class for simple QuickForm2 containers
81 *
82 * @category   HTML
83 * @package    HTML_QuickForm2
84 * @author     Alexey Borzov <avb@php.net>
85 * @author     Bertrand Mansion <golgote@mamasam.com>
86 * @version    Release: @package_version@
87 */
88abstract class HTML_QuickForm2_Container extends HTML_QuickForm2_Node
89    implements IteratorAggregate, Countable
90{
91   /**
92    * Array of elements contained in this container
93    * @var array
94    */
95    protected $elements = array();
96
97
98    public function setName($name)
99    {
100        $this->attributes['name'] = (string)$name;
101        return $this;
102    }
103
104    public function toggleFrozen($freeze = null)
105    {
106        if (null !== $freeze) {
107            foreach ($this as $child) {
108                $child->toggleFrozen($freeze);
109            }
110        }
111        return parent::toggleFrozen($freeze);
112    }
113
114    public function persistentFreeze($persistent = null)
115    {
116        if (null !== $persistent) {
117            foreach ($this as $child) {
118                $child->persistentFreeze($persistent);
119            }
120        }
121        return parent::persistentFreeze($persistent);
122    }
123
124   /**
125    * Whether container prepends its name to names of contained elements
126    *
127    * @return   bool
128    */
129    protected function prependsName()
130    {
131        return false;
132    }
133
134   /**
135    * Returns the element's value
136    *
137    * The default implementation for Containers is to return an array with
138    * contained elements' values. The array is indexed the same way $_GET and
139    * $_POST arrays would be for these elements.
140    *
141    * @return   array|null
142    */
143    public function getValue()
144    {
145        $values = array();
146        foreach ($this as $child) {
147            $value = $child->getValue();
148            if (null !== $value) {
149                if ($child instanceof HTML_QuickForm2_Container
150                    && !$child->prependsName()
151                ) {
152                    $values = self::arrayMerge($values, $value);
153                } else {
154                    $name = $child->getName();
155                    if (!strpos($name, '[')) {
156                        $values[$name] = $value;
157                    } else {
158                        $tokens   =  explode('[', str_replace(']', '', $name));
159                        $valueAry =& $values;
160                        do {
161                            $token = array_shift($tokens);
162                            if (!isset($valueAry[$token])) {
163                                $valueAry[$token] = array();
164                            }
165                            $valueAry =& $valueAry[$token];
166                        } while (count($tokens) > 1);
167                        $valueAry[$tokens[0]] = $value;
168                    }
169                }
170            }
171        }
172        return empty($values)? null: $this->applyFilters($values);
173    }
174
175   /**
176    * Merges two arrays
177    *
178    * Merges two arrays like the PHP function array_merge_recursive does,
179    * the difference being that existing integer keys will not be renumbered.
180    *
181    * @param    array
182    * @param    array
183    * @return   array   resulting array
184    */
185    protected static function arrayMerge($a, $b)
186    {
187        foreach ($b as $k => $v) {
188            if (!is_array($v) || isset($a[$k]) && !is_array($a[$k])) {
189                $a[$k] = $v;
190            } else {
191                $a[$k] = self::arrayMerge(isset($a[$k])? $a[$k]: array(), $v);
192            }
193        }
194        return $a;
195    }
196
197   /**
198    * Returns an array of this container's elements
199    *
200    * @return   array   Container elements
201    */
202    public function getElements()
203    {
204        return $this->elements;
205    }
206
207   /**
208    * Appends an element to the container
209    *
210    * If the element was previously added to the container or to another
211    * container, it is first removed there.
212    *
213    * @param    HTML_QuickForm2_Node     Element to add
214    * @return   HTML_QuickForm2_Node     Added element
215    * @throws   HTML_QuickForm2_InvalidArgumentException
216    */
217    public function appendChild(HTML_QuickForm2_Node $element)
218    {
219        if ($this === $element->getContainer()) {
220            $this->removeChild($element);
221        }
222        $element->setContainer($this);
223        $this->elements[] = $element;
224        return $element;
225    }
226
227   /**
228    * Appends an element to the container (possibly creating it first)
229    *
230    * If the first parameter is an instance of HTML_QuickForm2_Node then all
231    * other parameters are ignored and the method just calls {@link appendChild()}.
232    * In the other case the element is first created via
233    * {@link HTML_QuickForm2_Factory::createElement()} and then added via the
234    * same method. This is a convenience method to reduce typing and ease
235    * porting from HTML_QuickForm.
236    *
237    * @param    string|HTML_QuickForm2_Node  Either type name (treated
238    *               case-insensitively) or an element instance
239    * @param    mixed   Element name
240    * @param    mixed   Element attributes
241    * @param    array   Element-specific data
242    * @return   HTML_QuickForm2_Node     Added element
243    * @throws   HTML_QuickForm2_InvalidArgumentException
244    * @throws   HTML_QuickForm2_NotFoundException
245    */
246    public function addElement($elementOrType, $name = null, $attributes = null,
247                               array $data = array())
248    {
249        if ($elementOrType instanceof HTML_QuickForm2_Node) {
250            return $this->appendChild($elementOrType);
251        } else {
252            return $this->appendChild(HTML_QuickForm2_Factory::createElement(
253                $elementOrType, $name, $attributes, $data
254            ));
255        }
256    }
257
258   /**
259    * Removes the element from this container
260    *
261    * If the reference object is not given, the element will be appended.
262    *
263    * @param    HTML_QuickForm2_Node     Element to remove
264    * @return   HTML_QuickForm2_Node     Removed object
265    */
266    public function removeChild(HTML_QuickForm2_Node $element)
267    {
268
269        if ($element->getContainer() !== $this) {
270            throw new HTML_QuickForm2_NotFoundException(
271                "Element with name '".$element->getName()."' was not found"
272            );
273        }
274        foreach ($this as $key => $child){
275            if ($child === $element) {
276                unset($this->elements[$key]);
277                $element->setContainer(null);
278                break;
279            }
280        }
281        return $element;
282    }
283
284
285   /**
286    * Returns an element if its id is found
287    *
288    * @param    string  Element id to find
289    * @return   HTML_QuickForm2_Node|null
290    */
291    public function getElementById($id)
292    {
293        foreach ($this->getRecursiveIterator() as $element) {
294            if ($id == $element->getId()) {
295                return $element;
296            }
297        }
298        return null;
299    }
300
301   /**
302    * Returns an array of elements which name corresponds to element
303    *
304    * @param    string  Elements name to find
305    * @return   array
306    */
307    public function getElementsByName($name)
308    {
309        $found = array();
310        foreach ($this->getRecursiveIterator() as $element) {
311            if ($element->getName() == $name) {
312                $found[] = $element;
313            }
314        }
315        return $found;
316    }
317
318   /**
319    * Inserts an element in the container
320    *
321    * If the reference object is not given, the element will be appended.
322    *
323    * @param    HTML_QuickForm2_Node     Element to insert
324    * @param    HTML_QuickForm2_Node     Reference to insert before
325    * @return   HTML_QuickForm2_Node     Inserted element
326    */
327    public function insertBefore(HTML_QuickForm2_Node $element, HTML_QuickForm2_Node $reference = null)
328    {
329        if (null === $reference) {
330            return $this->appendChild($element);
331        }
332        $offset = 0;
333        foreach ($this as $child) {
334            if ($child === $reference) {
335                if ($this === $element->getContainer()) {
336                    $this->removeChild($element);
337                }
338                $element->setContainer($this);
339                array_splice($this->elements, $offset, 0, array($element));
340                return $element;
341            }
342            $offset++;
343        }
344        throw new HTML_QuickForm2_NotFoundException(
345            "Reference element with name '".$reference->getName()."' was not found"
346        );
347    }
348
349   /**
350    * Returns a recursive iterator for the container elements
351    *
352    * @return    HTML_QuickForm2_ContainerIterator
353    */
354    public function getIterator(): HTML_QuickForm2_ContainerIterator
355    {
356        return new HTML_QuickForm2_ContainerIterator($this);
357    }
358
359   /**
360    * Returns a recursive iterator iterator for the container elements
361    *
362    * @param    int     mode passed to RecursiveIteratorIterator
363    * @return   RecursiveIteratorIterator
364    */
365    public function getRecursiveIterator($mode = RecursiveIteratorIterator::SELF_FIRST): RecursiveIteratorIterator
366    {
367        return new RecursiveIteratorIterator(
368                        new HTML_QuickForm2_ContainerIterator($this), $mode
369                   );
370    }
371
372   /**
373    * Returns the number of elements in the container
374    *
375    * @return    int
376    */
377    public function count(): int
378    {
379        return count($this->elements);
380    }
381
382   /**
383    * Called when the element needs to update its value from form's data sources
384    *
385    * The default behaviour is just to call the updateValue() methods of
386    * contained elements, since default Container doesn't have any value itself
387    */
388    public function updateValue()
389    {
390        foreach ($this as $child) {
391            $child->updateValue();
392        }
393    }
394
395
396   /**
397    * Performs the server-side validation
398    *
399    * This method also calls validate() on all contained elements.
400    *
401    * @return   boolean Whether the container and all contained elements are valid
402    */
403    protected function validate()
404    {
405        $valid = parent::validate();
406        foreach ($this as $child) {
407            $valid = $child->validate() && $valid;
408        }
409        return $valid;
410    }
411
412   /**
413    * Appends an element to the container, creating it first
414    *
415    * The element will be created via {@link HTML_QuickForm2_Factory::createElement()}
416    * and then added via the {@link appendChild()} method.
417    * The element type is deduced from the method name.
418    * This is a convenience method to reduce typing.
419    *
420    * @param    mixed   Element name
421    * @param    mixed   Element attributes
422    * @param    array   Element-specific data
423    * @return   HTML_QuickForm2_Node     Added element
424    * @throws   HTML_QuickForm2_InvalidArgumentException
425    * @throws   HTML_QuickForm2_NotFoundException
426    */
427    public function __call($m, $a)
428    {
429        if (preg_match('/^(add)([a-zA-Z0-9_]+)$/', $m, $match)) {
430            if ($match[1] == 'add') {
431                $type = strtolower($match[2]);
432                $name = isset($a[0]) ? $a[0] : null;
433                $attr = isset($a[1]) ? $a[1] : null;
434                $data = isset($a[2]) ? $a[2] : array();
435                return $this->addElement($type, $name, $attr, $data);
436            }
437        }
438        trigger_error("Fatal error: Call to undefined method ".get_class($this)."::".$m."()", E_USER_ERROR);
439    }
440
441   /**
442    * Renders the container using the given renderer
443    *
444    * @param    HTML_QuickForm2_Renderer    Renderer instance
445    * @return   HTML_QuickForm2_Renderer
446    */
447    public function render(HTML_QuickForm2_Renderer $renderer)
448    {
449        foreach ($this->rules as $rule) {
450            if ($rule[1] & HTML_QuickForm2_Rule::RUNAT_CLIENT) {
451                $renderer->getJavascriptBuilder()->addRule($rule[0]);
452            }
453        }
454        $renderer->startContainer($this);
455        foreach ($this as $element) {
456            $element->render($renderer);
457        }
458        $renderer->finishContainer($this);
459        return $renderer;
460    }
461
462    public function __toString()
463    {
464        // require_once 'HTML/QuickForm2/Renderer.php';
465
466        return $this->render(HTML_QuickForm2_Renderer::factory('default'))->__toString();
467    }
468
469   /**
470    * Returns Javascript code for getting the element's value
471    *
472    * @return   string
473    */
474    public function getJavascriptValue()
475    {
476        $args = array();
477        foreach ($this as $child) {
478            if ($child instanceof HTML_QuickForm2_Container) {
479                $args[] = $child->getJavascriptValue();
480            } else {
481                $args[] = "'" . $child->getId() . "'";
482            }
483        }
484        return 'qf.form.getContainerValue(' . implode(', ', $args) . ')';
485    }
486}
487
488?>
489