MDL-21767 fixed input validation
[moodle.git] / lib / form / selectgroups.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
3 // +----------------------------------------------------------------------+
4 // | PHP version 4.0                                                      |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2003 The PHP Group                                |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.0 of the PHP license,       |
9 // | that is bundled with this package in the file LICENSE, and is        |
10 // | available at through the world-wide-web at                           |
11 // | http://www.php.net/license/2_02.txt.                                 |
12 // | If you did not receive a copy of the PHP license and are unable to   |
13 // | obtain it through the world-wide-web, please send a note to          |
14 // | license@php.net so we can mail you a copy immediately.               |
15 // +----------------------------------------------------------------------+
16 // | Authors: Adam Daniel <adaniel1@eesus.jnj.com>                        |
17 // |          Bertrand Mansion <bmansion@mamasam.com>                     |
18 // +----------------------------------------------------------------------+
20 require_once('HTML/QuickForm/element.php');
22 /**
23  * Class to dynamically create an HTML SELECT with all options grouped in optgroups
24  *
25  * @author       Adam Daniel <adaniel1@eesus.jnj.com>
26  * @author       Bertrand Mansion <bmansion@mamasam.com>
27  * @version      1.0
28  * @since        PHP4.04pl1
29  * @access       public
30  */
31 class MoodleQuickForm_selectgroups extends HTML_QuickForm_element {
33     // {{{ properties
35     /** add choose option */
36     var $showchoose = false;
38     /**
39      * Contains the select optgroups
40      *
41      * @var       array
42      * @since     1.0
43      * @access    private
44      */
45     var $_optGroups = array();
47     /**
48      * Default values of the SELECT
49      *
50      * @var       string
51      * @since     1.0
52      * @access    private
53      */
54     var $_values = null;
56     /**
57      * html for help button, if empty then no help
58      *
59      * @var string
60      */
61     var $_helpbutton='';
62     var $_hiddenLabel=false;
64     /**
65      * Class constructor
66      *
67      * @param     string    Select name attribute
68      * @param     mixed     Label(s) for the select
69      * @param     mixed     Data to be used to populate options
70      * @param     mixed     An array whose keys are labels for optgroups and whose values are arrays similar to those passed
71      *                          to the select element with keys that are values for options and values are strings for display.
72      * @param     mixed     Either a typical HTML attribute string or an associative array
73      * @param     bool      add standard moodle "Choose..." option as first item
74      * @since     1.0
75      * @access    public
76      * @return    void
77      */
78     function MoodleQuickForm_selectgroups($elementName=null, $elementLabel=null, $optgrps=null, $attributes=null, $showchoose=false)
79     {
80         $this->showchoose = $showchoose;
81         HTML_QuickForm_element::HTML_QuickForm_element($elementName, $elementLabel, $attributes);
82         $this->_persistantFreeze = true;
83         $this->_type = 'selectgroups';
84         if (isset($optgrps)) {
85             $this->loadArrayOptGroups($optgrps);
86         }
87     } //end constructor
89     // }}}
90     // {{{ apiVersion()
93     /**
94      * Sets the default values of the select box
95      *
96      * @param     mixed    $values  Array or comma delimited string of selected values
97      * @since     1.0
98      * @access    public
99      * @return    void
100      */
101     function setSelected($values)
102     {
103         if (is_string($values) && $this->getMultiple()) {
104             $values = split("[ ]?,[ ]?", $values);
105         }
106         if (is_array($values)) {
107             $this->_values = array_values($values);
108         } else {
109             $this->_values = array($values);
110         }
111     } //end func setSelected
113     // }}}
114     // {{{ getSelected()
116     /**
117      * Returns an array of the selected values
118      *
119      * @since     1.0
120      * @access    public
121      * @return    array of selected values
122      */
123     function getSelected()
124     {
125         return $this->_values;
126     } // end func getSelected
128     // }}}
129     // {{{ setName()
131     /**
132      * Sets the input field name
133      *
134      * @param     string    $name   Input field name attribute
135      * @since     1.0
136      * @access    public
137      * @return    void
138      */
139     function setName($name)
140     {
141         $this->updateAttributes(array('name' => $name));
142     } //end func setName
144     // }}}
145     // {{{ getName()
147     /**
148      * Returns the element name
149      *
150      * @since     1.0
151      * @access    public
152      * @return    string
153      */
154     function getName()
155     {
156         return $this->getAttribute('name');
157     } //end func getName
159     // }}}
160     // {{{ getPrivateName()
162     /**
163      * Returns the element name (possibly with brackets appended)
164      *
165      * @since     1.0
166      * @access    public
167      * @return    string
168      */
169     function getPrivateName()
170     {
171         if ($this->getAttribute('multiple')) {
172             return $this->getName() . '[]';
173         } else {
174             return $this->getName();
175         }
176     } //end func getPrivateName
178     // }}}
179     // {{{ setValue()
181     /**
182      * Sets the value of the form element
183      *
184      * @param     mixed    $values  Array or comma delimited string of selected values
185      * @since     1.0
186      * @access    public
187      * @return    void
188      */
189     function setValue($value)
190     {
191         $this->setSelected($value);
192     } // end func setValue
194     // }}}
195     // {{{ getValue()
197     /**
198      * Returns an array of the selected values
199      *
200      * @since     1.0
201      * @access    public
202      * @return    array of selected values
203      */
204     function getValue()
205     {
206         return $this->_values;
207     } // end func getValue
209     // }}}
210     // {{{ setSize()
212     /**
213      * Sets the select field size, only applies to 'multiple' selects
214      *
215      * @param     int    $size  Size of select  field
216      * @since     1.0
217      * @access    public
218      * @return    void
219      */
220     function setSize($size)
221     {
222         $this->updateAttributes(array('size' => $size));
223     } //end func setSize
225     // }}}
226     // {{{ getSize()
228     /**
229      * Returns the select field size
230      *
231      * @since     1.0
232      * @access    public
233      * @return    int
234      */
235     function getSize()
236     {
237         return $this->getAttribute('size');
238     } //end func getSize
240     // }}}
241     // {{{ setMultiple()
243     /**
244      * Sets the select mutiple attribute
245      *
246      * @param     bool    $multiple  Whether the select supports multi-selections
247      * @since     1.2
248      * @access    public
249      * @return    void
250      */
251     function setMultiple($multiple)
252     {
253         if ($multiple) {
254             $this->updateAttributes(array('multiple' => 'multiple'));
255         } else {
256             $this->removeAttribute('multiple');
257         }
258     } //end func setMultiple
260     // }}}
261     // {{{ getMultiple()
263     /**
264      * Returns the select mutiple attribute
265      *
266      * @since     1.2
267      * @access    public
268      * @return    bool    true if multiple select, false otherwise
269      */
270     function getMultiple()
271     {
272         return (bool)$this->getAttribute('multiple');
273     } //end func getMultiple
275     /**
276      * Loads the options from an associative array
277      *
278      * @param     array    $arr     Associative array of options
279      * @param     mixed    $values  (optional) Array or comma delimited string of selected values
280      * @since     1.0
281      * @access    public
282      * @return    PEAR_Error on error or true
283      * @throws    PEAR_Error
284      */
285     function loadArrayOptGroups($arr, $values=null)
286     {
287         if (!is_array($arr)) {
288             return PEAR::raiseError('Argument 1 of HTML_Select::loadArrayOptGroups is not a valid array');
289         }
290         if (isset($values)) {
291             $this->setSelected($values);
292         }
293         foreach ($arr as $key => $val) {
294             // Warning: new API since release 2.3
295             $this->addOptGroup($key, $val);
296         }
297         return true;
298     }
299     /**
300      * Adds a new OPTION to the SELECT
301      *
302      * @param     string    $text       Display text for the OPTION
303      * @param     string    $value      Value for the OPTION
304      * @param     mixed     $attributes Either a typical HTML attribute string
305      *                                  or an associative array
306      * @since     1.0
307      * @access    public
308      * @return    void
309      */
310     function addOptGroup($text, $value, $attributes=null)
311     {
312         if (null === $attributes) {
313             $attributes = array('label' => $text);
314         } else {
315             $attributes = $this->_parseAttributes($attributes);
316             $this->_updateAttrArray($attributes, array('label' => $text));
317         }
318         $index = count($this->_optGroups);
319         $this->_optGroups[$index] = array('attr' => $attributes);
320         $this->loadArrayOptions($index, $value);
321     }
323     /**
324      * Loads the options from an associative array
325      *
326      * @param     array    $arr     Associative array of options
327      * @param     mixed    $values  (optional) Array or comma delimited string of selected values
328      * @since     1.0
329      * @access    public
330      * @return    PEAR_Error on error or true
331      * @throws    PEAR_Error
332      */
333     function loadArrayOptions($optgroup, $arr, $values=null)
334     {
335         if (!is_array($arr)) {
336             return PEAR::raiseError('Argument 1 of HTML_Select::loadArray is not a valid array');
337         }
338         if (isset($values)) {
339             $this->setSelected($values);
340         }
341         foreach ($arr as $key => $val) {
342             // Warning: new API since release 2.3
343             $this->addOption($optgroup, $val, $key);
344         }
345         return true;
346     }
348     /**
349      * Adds a new OPTION to an optgroup
350      *
351      * @param     string    $text       Display text for the OPTION
352      * @param     string    $value      Value for the OPTION
353      * @param     mixed     $attributes Either a typical HTML attribute string
354      *                                  or an associative array
355      * @since     1.0
356      * @access    public
357      * @return    void
358      */
359     function addOption($optgroup, $text, $value, $attributes=null)
360     {
361         if (null === $attributes) {
362             $attributes = array('value' => $value);
363         } else {
364             $attributes = $this->_parseAttributes($attributes);
365             if (isset($attributes['selected'])) {
366                 // the 'selected' attribute will be set in toHtml()
367                 $this->_removeAttr('selected', $attributes);
368                 if (is_null($this->_values)) {
369                     $this->_values = array($value);
370                 } elseif (!in_array($value, $this->_values)) {
371                     $this->_values[] = $value;
372                 }
373             }
374             $this->_updateAttrArray($attributes, array('value' => $value));
375         }
376         $this->_optGroups[$optgroup]['options'][] = array('text' => $text, 'attr' => $attributes);
377     }
379     /**
380      * Returns the SELECT in HTML
381      *
382      * @since     1.0
383      * @access    public
384      * @return    string
385      */
386     function toHtml()
387     {
388         if ($this->_flagFrozen) {
389             return $this->getFrozenHtml();
390         } else {
391             $tabs    = $this->_getTabs();
392             $strHtml = '';
394             if ($this->getComment() != '') {
395                 $strHtml .= $tabs . '<!-- ' . $this->getComment() . " //-->\n";
396             }
398             if (!$this->getMultiple()) {
399                 $attrString = $this->_getAttrString($this->_attributes);
400             } else {
401                 $myName = $this->getName();
402                 $this->setName($myName . '[]');
403                 $attrString = $this->_getAttrString($this->_attributes);
404                 $this->setName($myName);
405             }
406             $strHtml .= $tabs;
407             if ($this->_hiddenLabel){
408                 $this->_generateId();
409                 $strHtml .= '<label class="accesshide" for="'.$this->getAttribute('id').'" >'.
410                             $this->getLabel().'</label>';
411             }
412             $strHtml .=  '<select' . $attrString . ">\n";
413             if ($this->showchoose) {
414                 $strHtml .= $tabs . "\t\t<option value=\"\">" . get_string('choose') . "...</option>\n";
415             }
416             foreach ($this->_optGroups as $optGroup) {
417                 if (empty($optGroup['options'])) {
418                     //xhtml strict
419                     continue;
420                 }
421                 $strHtml .= $tabs . "\t<optgroup" . ($this->_getAttrString($optGroup['attr'])) . '>';
422                 foreach ($optGroup['options'] as $option){
423                     if (is_array($this->_values) && in_array((string)$option['attr']['value'], $this->_values)) {
424                         $this->_updateAttrArray($option['attr'], array('selected' => 'selected'));
425                     }
426                     $strHtml .= $tabs . "\t\t<option" . $this->_getAttrString($option['attr']) . '>' .
427                                 $option['text'] . "</option>\n";
428                 }
429                 $strHtml .= $tabs . "\t</optgroup>\n";
430             }
431             return $strHtml . $tabs . '</select>';
432         }
433     } //end func toHtml
435     // }}}
436     // {{{ getFrozenHtml()
438     /**
439      * Returns the value of field without HTML tags
440      *
441      * @since     1.0
442      * @access    public
443      * @return    string
444      */
445     function getFrozenHtml()
446     {
447         $value = array();
448         if (is_array($this->_values)) {
449             foreach ($this->_values as $key => $val) {
450                 foreach ($this->_optGroups as $optGroup) {
451                     for ($i = 0, $optCount = count($optGroup['options']); $i < $optCount; $i++) {
452                         if ((string)$val == (string)$optGroup['options'][$i]['attr']['value']) {
453                             $value[$key] = $optGroup['options'][$i]['text'];
454                             break;
455                         }
456                     }
457                 }
458             }
459         }
460         $html = empty($value)? '&nbsp;': join('<br />', $value);
461         if ($this->_persistantFreeze) {
462             $name = $this->getPrivateName();
463             // Only use id attribute if doing single hidden input
464             if (1 == count($value)) {
465                 $id     = $this->getAttribute('id');
466                 $idAttr = isset($id)? array('id' => $id): array();
467             } else {
468                 $idAttr = array();
469             }
470             foreach ($value as $key => $item) {
471                 $html .= '<input' . $this->_getAttrString(array(
472                              'type'  => 'hidden',
473                              'name'  => $name,
474                              'value' => $this->_values[$key]
475                          ) + $idAttr) . ' />';
476             }
477         }
478         return $html;
479     } //end func getFrozenHtml
481     // }}}
482     // {{{ exportValue()
484    /**
485     * We check the options and return only the values that _could_ have been
486     * selected. We also return a scalar value if select is not "multiple"
487     */
488     function exportValue(&$submitValues, $assoc = false)
489     {
490         if (empty($this->_optGroups)) {
491             return $this->_prepareValue(null, $assoc);
492         }
494         $value = $this->_findValue($submitValues);
495         if (is_null($value)) {
496             $value = $this->getValue();
497         }
498         $value = (array)$value;
500         $cleaned = array();
501         foreach ($value as $v) {
502             foreach ($this->_optGroups as $optGroup){
503                 if (empty($optGroup['options'])) {
504                     continue;
505                 }
506                 foreach ($optGroup['options'] as $option) {
507                     if ((string)$option['attr']['value'] === (string)$v) {
508                         $cleaned[] = (string)$option['attr']['value'];
509                         break;
510                     }
511                 }
512             }
513         }
515         if (empty($cleaned)) {
516             return $this->_prepareValue(null, $assoc);
517         }
518         if ($this->getMultiple()) {
519             return $this->_prepareValue($cleaned, $assoc);
520         } else {
521             return $this->_prepareValue($cleaned[0], $assoc);
522         }
523     }
524     
525     // }}}
526     // {{{ onQuickFormEvent()
528     function onQuickFormEvent($event, $arg, &$caller)
529     {
530         if ('updateValue' == $event) {
531             $value = $this->_findValue($caller->_constantValues);
532             if (null === $value) {
533                 $value = $this->_findValue($caller->_submitValues);
534                 // Fix for bug #4465 & #5269
535                 // XXX: should we push this to element::onQuickFormEvent()?
536                 if (null === $value && (!$caller->isSubmitted() || !$this->getMultiple())) {
537                     $value = $this->_findValue($caller->_defaultValues);
538                 }
539             }
540             if (null !== $value) {
541                 $this->setValue($value);
542             }
543             return true;
544         } else {
545             return parent::onQuickFormEvent($event, $arg, $caller);
546         }
547     }
548     function setHiddenLabel($hiddenLabel){
549         $this->_hiddenLabel = $hiddenLabel;
550     }
551    /**
552     * Automatically generates and assigns an 'id' attribute for the element.
553     *
554     * Currently used to ensure that labels work on radio buttons and
555     * checkboxes. Per idea of Alexander Radivanovich.
556     * Overriden in moodleforms to remove qf_ prefix.
557     *
558     * @access private
559     * @return void
560     */
561     function _generateId()
562     {
563         static $idx = 1;
565         if (!$this->getAttribute('id')) {
566             $this->updateAttributes(array('id' => 'id_'. substr(md5(microtime() . $idx++), 0, 6)));
567         }
568     } // end func _generateId
569     /**
570      * set html for help button
571      *
572      * @access   public
573      * @param array $help array of arguments to make a help button
574      * @param string $function function name to call to get html
575      */
576     function setHelpButton($helpbuttonargs, $function='helpbutton'){
577         debugging('component setHelpButton() is not used any more, please use $mform->setHelpButton() instead');
578     }
579     /**
580      * get html for help button
581      *
582      * @access   public
583      * @return  string html for help button
584      */
585     function getHelpButton(){
586         return $this->_helpbutton;
587     }
589     /**
590      * Slightly different container template when frozen. Don't want to use a label tag
591      * with a for attribute in that case for the element label but instead use a div.
592      * Templates are defined in renderer constructor.
593      *
594      * @return string
595      */
596     function getElementTemplateType(){
597         if ($this->_flagFrozen){
598             return 'static';
599         } else {
600             return 'default';
601         }
602     }