Configure the dmca form site on ridley
[chef.git] / cookbooks / dmca / files / default / html / HTML / QuickForm / hierselect.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4 /**
5  * Hierarchical select element
6  *
7  * PHP versions 4 and 5
8  *
9  * LICENSE: This source file is subject to version 3.01 of the PHP license
10  * that is available through the world-wide-web at the following URI:
11  * http://www.php.net/license/3_01.txt If you did not receive a copy of
12  * the PHP License and are unable to obtain it through the web, please
13  * send a note to license@php.net so we can mail you a copy immediately.
14  *
15  * @category    HTML
16  * @package     HTML_QuickForm
17  * @author      Herim Vasquez <vasquezh@iro.umontreal.ca>
18  * @author      Bertrand Mansion <bmansion@mamasam.com>
19  * @author      Alexey Borzov <avb@php.net>
20  * @copyright   2001-2011 The PHP Group
21  * @license     http://www.php.net/license/3_01.txt PHP License 3.01
22  * @version     CVS: $Id$
23  * @link        http://pear.php.net/package/HTML_QuickForm
24  */
25
26 /**
27  * Class for a group of form elements
28  */
29 require_once 'HTML/QuickForm/group.php';
30 /**
31  * Class for <select></select> elements
32  */
33 require_once 'HTML/QuickForm/select.php';
34 /**
35  * Static utility methods
36  */
37 require_once 'HTML/QuickForm/utils.php';
38
39 /**
40  * Hierarchical select element
41  *
42  * Class to dynamically create two or more HTML Select elements
43  * The first select changes the content of the second select and so on.
44  * This element is considered as a group. Selects will be named
45  * groupName[0], groupName[1], groupName[2]...
46  *
47  * @category    HTML
48  * @package     HTML_QuickForm
49  * @author      Herim Vasquez <vasquezh@iro.umontreal.ca>
50  * @author      Bertrand Mansion <bmansion@mamasam.com>
51  * @author      Alexey Borzov <avb@php.net>
52  * @version     Release: 3.2.16
53  * @since       3.1
54  */
55 class HTML_QuickForm_hierselect extends HTML_QuickForm_group
56 {
57     // {{{ properties
58
59     /**
60      * Options for all the select elements
61      *
62      * @see       setOptions()
63      * @var       array
64      * @access    private
65      */
66     var $_options = array();
67
68     /**
69      * Number of select elements on this group
70      *
71      * @var       int
72      * @access    private
73      */
74     var $_nbElements = 0;
75
76     /**
77      * The javascript used to set and change the options
78      *
79      * @var       string
80      * @access    private
81      */
82     var $_js = '';
83
84     // }}}
85     // {{{ constructor
86
87     /**
88      * Class constructor
89      *
90      * @param     string    $elementName    (optional)Input field name attribute
91      * @param     string    $elementLabel   (optional)Input field label in form
92      * @param     mixed     $attributes     (optional)Either a typical HTML attribute string
93      *                                      or an associative array. Date format is passed along the attributes.
94      * @param     mixed     $separator      (optional)Use a string for one separator,
95      *                                      use an array to alternate the separators.
96      * @access    public
97      * @return    void
98      */
99     function HTML_QuickForm_hierselect($elementName=null, $elementLabel=null, $attributes=null, $separator=null)
100     {
101         $this->HTML_QuickForm_element($elementName, $elementLabel, $attributes);
102         $this->_persistantFreeze = true;
103         if (isset($separator)) {
104             $this->_separator = $separator;
105         }
106         $this->_type = 'hierselect';
107         $this->_appendName = true;
108     } //end constructor
109
110     // }}}
111     // {{{ setOptions()
112
113     /**
114      * Initialize the array structure containing the options for each select element.
115      * Call the functions that actually do the magic.
116      *
117      * Format is a bit more complex than for a simple select as we need to know
118      * which options are related to the ones in the previous select:
119      *
120      * Ex:
121      * <code>
122      * // first select
123      * $select1[0] = 'Pop';
124      * $select1[1] = 'Classical';
125      * $select1[2] = 'Funeral doom';
126      *
127      * // second select
128      * $select2[0][0] = 'Red Hot Chil Peppers';
129      * $select2[0][1] = 'The Pixies';
130      * $select2[1][0] = 'Wagner';
131      * $select2[1][1] = 'Strauss';
132      * $select2[2][0] = 'Pantheist';
133      * $select2[2][1] = 'Skepticism';
134      *
135      * // If only need two selects
136      * //     - and using the deprecated functions
137      * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:');
138      * $sel->setMainOptions($select1);
139      * $sel->setSecOptions($select2);
140      *
141      * //     - and using the new setOptions function
142      * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:');
143      * $sel->setOptions(array($select1, $select2));
144      *
145      * // If you have a third select with prices for the cds
146      * $select3[0][0][0] = '15.00$';
147      * $select3[0][0][1] = '17.00$';
148      * // etc
149      *
150      * // You can now use
151      * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:');
152      * $sel->setOptions(array($select1, $select2, $select3));
153      * </code>
154      *
155      * @param     array    $options    Array of options defining each element
156      * @access    public
157      * @return    void
158      */
159     function setOptions($options)
160     {
161         $this->_options = $options;
162
163         if (empty($this->_elements)) {
164             $this->_nbElements = count($this->_options);
165             $this->_createElements();
166         } else {
167             // setDefaults has probably been called before this function
168             // check if all elements have been created
169             $totalNbElements = count($this->_options);
170             for ($i = $this->_nbElements; $i < $totalNbElements; $i ++) {
171                 $this->_elements[] =& new HTML_QuickForm_select($i, null, array(), $this->getAttributes());
172                 $this->_nbElements++;
173             }
174         }
175
176         $this->_setOptions();
177     } // end func setMainOptions
178
179     // }}}
180     // {{{ setMainOptions()
181
182     /**
183      * Sets the options for the first select element. Deprecated. setOptions() should be used.
184      *
185      * @param     array     $array    Options for the first select element
186      *
187      * @access    public
188      * @deprecated          Deprecated since release 3.2.2
189      * @return    void
190      */
191     function setMainOptions($array)
192     {
193         $this->_options[0] = $array;
194
195         if (empty($this->_elements)) {
196             $this->_nbElements = 2;
197             $this->_createElements();
198         }
199     } // end func setMainOptions
200
201     // }}}
202     // {{{ setSecOptions()
203
204     /**
205      * Sets the options for the second select element. Deprecated. setOptions() should be used.
206      * The main _options array is initialized and the _setOptions function is called.
207      *
208      * @param     array     $array    Options for the second select element
209      *
210      * @access    public
211      * @deprecated          Deprecated since release 3.2.2
212      * @return    void
213      */
214     function setSecOptions($array)
215     {
216         $this->_options[1] = $array;
217
218         if (empty($this->_elements)) {
219             $this->_nbElements = 2;
220             $this->_createElements();
221         } else {
222             // setDefaults has probably been called before this function
223             // check if all elements have been created
224             $totalNbElements = 2;
225             for ($i = $this->_nbElements; $i < $totalNbElements; $i ++) {
226                 $this->_elements[] =& new HTML_QuickForm_select($i, null, array(), $this->getAttributes());
227                 $this->_nbElements++;
228             }
229         }
230
231         $this->_setOptions();
232     } // end func setSecOptions
233
234     // }}}
235     // {{{ _setOptions()
236
237     /**
238      * Sets the options for each select element
239      *
240      * @access    private
241      * @return    void
242      */
243     function _setOptions()
244     {
245         $arrayKeys = array();
246         foreach (array_keys($this->_elements) AS $key) {
247             if (isset($this->_options[$key])) {
248                 if ((empty($arrayKeys)) || HTML_QuickForm_utils::recursiveIsset($this->_options[$key], $arrayKeys)) {
249                     $array = empty($arrayKeys) ? $this->_options[$key] : HTML_QuickForm_utils::recursiveValue($this->_options[$key], $arrayKeys);
250                     if (is_array($array)) {
251                         $select =& $this->_elements[$key];
252                         $select->_options = array();
253                         $select->loadArray($array);
254
255                         $value = is_array($v = $select->getValue()) ? $v[0] : key($array);
256                         $arrayKeys[] = $value;
257                     }
258                 }
259             }
260         }
261     } // end func _setOptions
262
263     // }}}
264     // {{{ setValue()
265
266     /**
267      * Sets values for group's elements
268      *
269      * @param     array     $value    An array of 2 or more values, for the first,
270      *                                the second, the third etc. select
271      *
272      * @access    public
273      * @return    void
274      */
275     function setValue($value)
276     {
277         // fix for bug #6766. Hope this doesn't break anything more
278         // after bug #7961. Forgot that _nbElements was used in
279         // _createElements() called in several places...
280         $this->_nbElements = max($this->_nbElements, count($value));
281         parent::setValue($value);
282         $this->_setOptions();
283     } // end func setValue
284
285     // }}}
286     // {{{ _createElements()
287
288     /**
289      * Creates all the elements for the group
290      *
291      * @access    private
292      * @return    void
293      */
294     function _createElements()
295     {
296         for ($i = 0; $i < $this->_nbElements; $i++) {
297             $this->_elements[] =& new HTML_QuickForm_select($i, null, array(), $this->getAttributes());
298         }
299     } // end func _createElements
300
301     // }}}
302     // {{{ toHtml()
303
304     function toHtml()
305     {
306         $this->_js = '';
307         if (!$this->_flagFrozen) {
308             // set the onchange attribute for each element except last
309             $keys     = array_keys($this->_elements);
310             $onChange = array();
311             for ($i = 0; $i < count($keys) - 1; $i++) {
312                 $select =& $this->_elements[$keys[$i]];
313                 $onChange[$i] = $select->getAttribute('onchange');
314                 $select->updateAttributes(
315                     array('onchange' => '_hs_swapOptions(this.form, \'' . $this->_escapeString($this->getName()) . '\', ' . $keys[$i] . ');' . $onChange[$i])
316                 );
317             }
318
319             // create the js function to call
320             if (!defined('HTML_QUICKFORM_HIERSELECT_EXISTS')) {
321                 $this->_js .= <<<JAVASCRIPT
322 function _hs_findOptions(ary, keys)
323 {
324     if (ary == undefined) {
325         return {};
326     }
327     var key = keys.shift();
328     if (!key in ary) {
329         return {};
330     } else if (0 == keys.length) {
331         return ary[key];
332     } else {
333         return _hs_findOptions(ary[key], keys);
334     }
335 }
336
337 function _hs_findSelect(form, groupName, selectIndex)
338 {
339     if (groupName+'['+ selectIndex +']' in form) {
340         return form[groupName+'['+ selectIndex +']'];
341     } else {
342         return form[groupName+'['+ selectIndex +'][]'];
343     }
344 }
345
346 function _hs_unescapeEntities(str)
347 {
348     var div = document.createElement('div');
349     div.innerHTML = str;
350     return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
351 }
352
353 function _hs_replaceOptions(ctl, options)
354 {
355     var j = 0;
356     ctl.options.length = 0;
357     for (var i = 0; i < options.values.length; i++) {
358         ctl.options[i] = new Option(
359             (-1 == String(options.texts[i]).indexOf('&'))? options.texts[i]: _hs_unescapeEntities(options.texts[i]),
360             options.values[i], false, false
361         );
362     }
363 }
364
365 function _hs_setValue(ctl, value)
366 {
367     var testValue = {};
368     if (value instanceof Array) {
369         for (var i = 0; i < value.length; i++) {
370             testValue[value[i]] = true;
371         }
372     } else {
373         testValue[value] = true;
374     }
375     for (var i = 0; i < ctl.options.length; i++) {
376         if (ctl.options[i].value in testValue) {
377             ctl.options[i].selected = true;
378         }
379     }
380 }
381
382 function _hs_swapOptions(form, groupName, selectIndex)
383 {
384     var hsValue = [];
385     for (var i = 0; i <= selectIndex; i++) {
386         hsValue[i] = _hs_findSelect(form, groupName, i).value;
387     }
388
389     _hs_replaceOptions(_hs_findSelect(form, groupName, selectIndex + 1),
390                        _hs_findOptions(_hs_options[groupName][selectIndex], hsValue));
391     if (selectIndex + 1 < _hs_options[groupName].length) {
392         _hs_swapOptions(form, groupName, selectIndex + 1);
393     }
394 }
395
396 function _hs_onReset(form, groupNames)
397 {
398     for (var i = 0; i < groupNames.length; i++) {
399         try {
400             for (var j = 0; j <= _hs_options[groupNames[i]].length; j++) {
401                 _hs_setValue(_hs_findSelect(form, groupNames[i], j), _hs_defaults[groupNames[i]][j]);
402                 if (j < _hs_options[groupNames[i]].length) {
403                     _hs_replaceOptions(_hs_findSelect(form, groupNames[i], j + 1),
404                                        _hs_findOptions(_hs_options[groupNames[i]][j], _hs_defaults[groupNames[i]].slice(0, j + 1)));
405                 }
406             }
407         } catch (e) {
408             if (!(e instanceof TypeError)) {
409                 throw e;
410             }
411         }
412     }
413 }
414
415 function _hs_setupOnReset(form, groupNames)
416 {
417     setTimeout(function() { _hs_onReset(form, groupNames); }, 25);
418 }
419
420 function _hs_onReload()
421 {
422     var ctl;
423     for (var i = 0; i < document.forms.length; i++) {
424         for (var j in _hs_defaults) {
425             if (ctl = _hs_findSelect(document.forms[i], j, 0)) {
426                 for (var k = 0; k < _hs_defaults[j].length; k++) {
427                     _hs_setValue(_hs_findSelect(document.forms[i], j, k), _hs_defaults[j][k]);
428                 }
429             }
430         }
431     }
432
433     if (_hs_prevOnload) {
434         _hs_prevOnload();
435     }
436 }
437
438 var _hs_prevOnload = null;
439 if (window.onload) {
440     _hs_prevOnload = window.onload;
441 }
442 window.onload = _hs_onReload;
443
444 var _hs_options = {};
445 var _hs_defaults = {};
446
447 JAVASCRIPT;
448                 define('HTML_QUICKFORM_HIERSELECT_EXISTS', true);
449             }
450             // option lists
451             $jsParts = array();
452             for ($i = 1; $i < $this->_nbElements; $i++) {
453                 $jsParts[] = $this->_convertArrayToJavascript($this->_prepareOptions($this->_options[$i], $i));
454             }
455             $this->_js .= "\n_hs_options['" . $this->_escapeString($this->getName()) . "'] = [\n" .
456                           implode(",\n", $jsParts) .
457                           "\n];\n";
458             // default value; if we don't actually have any values yet just use
459             // the first option (for single selects) or empty array (for multiple)
460             $values = array();
461             foreach (array_keys($this->_elements) as $key) {
462                 if (is_array($v = $this->_elements[$key]->getValue())) {
463                     $values[] = count($v) > 1? $v: $v[0];
464                 } else {
465                     // XXX: accessing the supposedly private _options array
466                     $values[] = $this->_elements[$key]->getMultiple() || empty($this->_elements[$key]->_options[0])?
467                                 array():
468                                 $this->_elements[$key]->_options[0]['attr']['value'];
469                 }
470             }
471             $this->_js .= "_hs_defaults['" . $this->_escapeString($this->getName()) . "'] = " .
472                           $this->_convertArrayToJavascript($values) . ";\n";
473         }
474         include_once('HTML/QuickForm/Renderer/Default.php');
475         $renderer =& new HTML_QuickForm_Renderer_Default();
476         $renderer->setElementTemplate('{element}');
477         parent::accept($renderer);
478
479         if (!empty($onChange)) {
480             $keys     = array_keys($this->_elements);
481             for ($i = 0; $i < count($keys) - 1; $i++) {
482                 $this->_elements[$keys[$i]]->updateAttributes(array('onchange' => $onChange[$i]));
483             }
484         }
485         return (empty($this->_js)? '': "<script type=\"text/javascript\">\n//<![CDATA[\n" . $this->_js . "//]]>\n</script>") .
486                $renderer->toHtml();
487     } // end func toHtml
488
489     // }}}
490     // {{{ accept()
491
492     function accept(&$renderer, $required = false, $error = null)
493     {
494         $renderer->renderElement($this, $required, $error);
495     } // end func accept
496
497     // }}}
498     // {{{ onQuickFormEvent()
499
500     function onQuickFormEvent($event, $arg, &$caller)
501     {
502         if ('updateValue' == $event) {
503             // we need to call setValue() so that the secondary option
504             // matches the main option
505             return HTML_QuickForm_element::onQuickFormEvent($event, $arg, $caller);
506         } else {
507             $ret = parent::onQuickFormEvent($event, $arg, $caller);
508             // add onreset handler to form to properly reset hierselect (see bug #2970)
509             if ('addElement' == $event) {
510                 $onReset = $caller->getAttribute('onreset');
511                 if (strlen($onReset)) {
512                     if (strpos($onReset, '_hs_setupOnReset')) {
513                         $caller->updateAttributes(array('onreset' => str_replace('_hs_setupOnReset(this, [', "_hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "', ", $onReset)));
514                     } else {
515                         $caller->updateAttributes(array('onreset' => "var temp = function() { {$onReset} } ; if (!temp()) { return false; } ; if (typeof _hs_setupOnReset != 'undefined') { return _hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "']); } "));
516                     }
517                 } else {
518                     $caller->updateAttributes(array('onreset' => "if (typeof _hs_setupOnReset != 'undefined') { return _hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "']); } "));
519                 }
520             }
521             return $ret;
522         }
523     } // end func onQuickFormEvent
524
525     // }}}
526     // {{{ _prepareOptions()
527
528    /**
529     * Prepares options for JS encoding
530     *
531     * We need to preserve order of options when adding them via javascript, so
532     * cannot use object literal and for/in loop (see bug #16603). Therefore we
533     * convert an associative array of options to two arrays of their values
534     * and texts. Backport from HTML_QuickForm2.
535     *
536     * @param    array   Options array
537     * @param    int     Depth within options array
538     * @link     http://pear.php.net/bugs/bug.php?id=16603
539     * @return   array
540     * @access   private
541     */
542     function _prepareOptions($ary, $depth)
543     {
544         if (!is_array($ary)) {
545             $ret = $ary;
546         } elseif (0 == $depth) {
547             $ret = array('values' => array_keys($ary), 'texts' => array_values($ary));
548         } else {
549             $ret = array();
550             foreach ($ary as $k => $v) {
551                 $ret[$k] = $this->_prepareOptions($v, $depth - 1);
552             }
553         }
554         return $ret;
555     }
556
557     // }}}
558     // {{{ _convertArrayToJavascript()
559
560    /**
561     * Converts PHP array to its Javascript analog
562     *
563     * @access private
564     * @param  array     PHP array to convert
565     * @return string    Javascript representation of the value
566     */
567     function _convertArrayToJavascript($array)
568     {
569         if (!is_array($array)) {
570             return $this->_convertScalarToJavascript($array);
571         } elseif (count($array) && array_keys($array) != range(0, count($array) - 1)) {
572             return '{' . implode(',', array_map(
573                 array($this, '_encodeNameValue'),
574                 array_keys($array), array_values($array)
575             )) . '}';
576         } else {
577             return '[' . implode(',', array_map(
578                 array($this, '_convertArrayToJavascript'),
579                 $array
580             )) . ']';
581         }
582     }
583
584     // }}}
585     // {{{ _encodeNameValue()
586
587    /**
588     * Callback for array_map used to generate JS name-value pairs
589     *
590     * @param    mixed
591     * @param    mixed
592     * @return   string
593     */
594     function _encodeNameValue($name, $value)
595     {
596         return $this->_convertScalarToJavascript((string)$name) . ':'
597                . $this->_convertArrayToJavascript($value);
598     }
599
600     // }}}
601     // {{{ _convertScalarToJavascript()
602
603    /**
604     * Converts PHP's scalar value to its Javascript analog
605     *
606     * @access private
607     * @param  mixed     PHP value to convert
608     * @return string    Javascript representation of the value
609     */
610     function _convertScalarToJavascript($val)
611     {
612         if (is_bool($val)) {
613             return $val ? 'true' : 'false';
614         } elseif (is_int($val) || is_double($val)) {
615             return $val;
616         } elseif (is_string($val)) {
617             return "'" . $this->_escapeString($val) . "'";
618         } elseif (is_null($val)) {
619             return 'null';
620         } else {
621             // don't bother
622             return '{}';
623         }
624     }
625
626     // }}}
627     // {{{ _escapeString()
628
629    /**
630     * Quotes the string so that it can be used in Javascript string constants
631     *
632     * @access private
633     * @param  string
634     * @return string
635     */
636     function _escapeString($str)
637     {
638         return strtr($str,array(
639             "\r"    => '\r',
640             "\n"    => '\n',
641             "\t"    => '\t',
642             "'"     => "\\'",
643             '"'     => '\"',
644             '\\'    => '\\\\'
645         ));
646     }
647
648     // }}}
649 } // end class HTML_QuickForm_hierselect
650 ?>