MDL-38455 form: More consistency in section states
[moodle.git] / lib / formslib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * formslib.php - library of classes for creating forms in Moodle, based on PEAR QuickForms.
19  *
20  * To use formslib then you will want to create a new file purpose_form.php eg. edit_form.php
21  * and you want to name your class something like {modulename}_{purpose}_form. Your class will
22  * extend moodleform overriding abstract classes definition and optionally defintion_after_data
23  * and validation.
24  *
25  * See examples of use of this library in course/edit.php and course/edit_form.php
26  *
27  * A few notes :
28  *      form definition is used for both printing of form and processing and should be the same
29  *              for both or you may lose some submitted data which won't be let through.
30  *      you should be using setType for every form element except select, radio or checkbox
31  *              elements, these elements clean themselves.
32  *
33  * @package   core_form
34  * @copyright 2006 Jamie Pratt <me@jamiep.org>
35  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36  */
38 defined('MOODLE_INTERNAL') || die();
40 /** setup.php includes our hacked pear libs first */
41 require_once 'HTML/QuickForm.php';
42 require_once 'HTML/QuickForm/DHTMLRulesTableless.php';
43 require_once 'HTML/QuickForm/Renderer/Tableless.php';
44 require_once 'HTML/QuickForm/Rule.php';
46 require_once $CFG->libdir.'/filelib.php';
48 /**
49  * EDITOR_UNLIMITED_FILES - hard-coded value for the 'maxfiles' option
50  */
51 define('EDITOR_UNLIMITED_FILES', -1);
53 /**
54  * Callback called when PEAR throws an error
55  *
56  * @param PEAR_Error $error
57  */
58 function pear_handle_error($error){
59     echo '<strong>'.$error->GetMessage().'</strong> '.$error->getUserInfo();
60     echo '<br /> <strong>Backtrace </strong>:';
61     print_object($error->backtrace);
62 }
64 if (!empty($CFG->debug) and ($CFG->debug >= DEBUG_ALL or $CFG->debug == -1)){
65     //TODO: this is a wrong place to init PEAR!
66     $GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_CALLBACK;
67     $GLOBALS['_PEAR_default_error_options'] = 'pear_handle_error';
68 }
70 /**
71  * Initalize javascript for date type form element
72  *
73  * @staticvar bool $done make sure it gets initalize once.
74  * @global moodle_page $PAGE
75  */
76 function form_init_date_js() {
77     global $PAGE;
78     static $done = false;
79     if (!$done) {
80         $module   = 'moodle-form-dateselector';
81         $function = 'M.form.dateselector.init_date_selectors';
82         $config = array(array(
83             'firstdayofweek'    => get_string('firstdayofweek', 'langconfig'),
84             'mon'               => date_format_string(strtotime("Monday"), '%a', 99),
85             'tue'               => date_format_string(strtotime("Tuesday"), '%a', 99),
86             'wed'               => date_format_string(strtotime("Wednesday"), '%a', 99),
87             'thu'               => date_format_string(strtotime("Thursday"), '%a', 99),
88             'fri'               => date_format_string(strtotime("Friday"), '%a', 99),
89             'sat'               => date_format_string(strtotime("Saturday"), '%a', 99),
90             'sun'               => date_format_string(strtotime("Sunday"), '%a', 99),
91             'january'           => date_format_string(strtotime("January 1"), '%B', 99),
92             'february'          => date_format_string(strtotime("February 1"), '%B', 99),
93             'march'             => date_format_string(strtotime("March 1"), '%B', 99),
94             'april'             => date_format_string(strtotime("April 1"), '%B', 99),
95             'may'               => date_format_string(strtotime("May 1"), '%B', 99),
96             'june'              => date_format_string(strtotime("June 1"), '%B', 99),
97             'july'              => date_format_string(strtotime("July 1"), '%B', 99),
98             'august'            => date_format_string(strtotime("August 1"), '%B', 99),
99             'september'         => date_format_string(strtotime("September 1"), '%B', 99),
100             'october'           => date_format_string(strtotime("October 1"), '%B', 99),
101             'november'          => date_format_string(strtotime("November 1"), '%B', 99),
102             'december'          => date_format_string(strtotime("December 1"), '%B', 99)
103         ));
104         $PAGE->requires->yui_module($module, $function, $config);
105         $done = true;
106     }
109 /**
110  * Wrapper that separates quickforms syntax from moodle code
111  *
112  * Moodle specific wrapper that separates quickforms syntax from moodle code. You won't directly
113  * use this class you should write a class definition which extends this class or a more specific
114  * subclass such a moodleform_mod for each form you want to display and/or process with formslib.
115  *
116  * You will write your own definition() method which performs the form set up.
117  *
118  * @package   core_form
119  * @copyright 2006 Jamie Pratt <me@jamiep.org>
120  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
121  * @todo      MDL-19380 rethink the file scanning
122  */
123 abstract class moodleform {
124     /** @var string name of the form */
125     protected $_formname;       // form name
127     /** @var MoodleQuickForm quickform object definition */
128     protected $_form;
130     /** @var array globals workaround */
131     protected $_customdata;
133     /** @var object definition_after_data executed flag */
134     protected $_definition_finalized = false;
136     /**
137      * The constructor function calls the abstract function definition() and it will then
138      * process and clean and attempt to validate incoming data.
139      *
140      * It will call your custom validate method to validate data and will also check any rules
141      * you have specified in definition using addRule
142      *
143      * The name of the form (id attribute of the form) is automatically generated depending on
144      * the name you gave the class extending moodleform. You should call your class something
145      * like
146      *
147      * @param mixed $action the action attribute for the form. If empty defaults to auto detect the
148      *              current url. If a moodle_url object then outputs params as hidden variables.
149      * @param mixed $customdata if your form defintion method needs access to data such as $course
150      *              $cm, etc. to construct the form definition then pass it in this array. You can
151      *              use globals for somethings.
152      * @param string $method if you set this to anything other than 'post' then _GET and _POST will
153      *               be merged and used as incoming data to the form.
154      * @param string $target target frame for form submission. You will rarely use this. Don't use
155      *               it if you don't need to as the target attribute is deprecated in xhtml strict.
156      * @param mixed $attributes you can pass a string of html attributes here or an array.
157      * @param bool $editable
158      */
159     function moodleform($action=null, $customdata=null, $method='post', $target='', $attributes=null, $editable=true) {
160         global $CFG, $FULLME;
161         // no standard mform in moodle should allow autocomplete with the exception of user signup
162         if (empty($attributes)) {
163             $attributes = array('autocomplete'=>'off');
164         } else if (is_array($attributes)) {
165             $attributes['autocomplete'] = 'off';
166         } else {
167             if (strpos($attributes, 'autocomplete') === false) {
168                 $attributes .= ' autocomplete="off" ';
169             }
170         }
172         if (empty($action)){
173             // do not rely on PAGE->url here because dev often do not setup $actualurl properly in admin_externalpage_setup()
174             $action = strip_querystring($FULLME);
175             if (!empty($CFG->sslproxy)) {
176                 // return only https links when using SSL proxy
177                 $action = preg_replace('/^http:/', 'https:', $action, 1);
178             }
179             //TODO: use following instead of FULLME - see MDL-33015
180             //$action = strip_querystring(qualified_me());
181         }
182         // Assign custom data first, so that get_form_identifier can use it.
183         $this->_customdata = $customdata;
184         $this->_formname = $this->get_form_identifier();
186         $this->_form = new MoodleQuickForm($this->_formname, $method, $action, $target, $attributes);
187         if (!$editable){
188             $this->_form->hardFreeze();
189         }
191         $this->definition();
193         $this->_form->addElement('hidden', 'sesskey', null); // automatic sesskey protection
194         $this->_form->setType('sesskey', PARAM_RAW);
195         $this->_form->setDefault('sesskey', sesskey());
196         $this->_form->addElement('hidden', '_qf__'.$this->_formname, null);   // form submission marker
197         $this->_form->setType('_qf__'.$this->_formname, PARAM_RAW);
198         $this->_form->setDefault('_qf__'.$this->_formname, 1);
199         $this->_form->_setDefaultRuleMessages();
201         // we have to know all input types before processing submission ;-)
202         $this->_process_submission($method);
203     }
205     /**
206      * It should returns unique identifier for the form.
207      * Currently it will return class name, but in case two same forms have to be
208      * rendered on same page then override function to get unique form identifier.
209      * e.g This is used on multiple self enrollments page.
210      *
211      * @return string form identifier.
212      */
213     protected function get_form_identifier() {
214         return get_class($this);
215     }
217     /**
218      * To autofocus on first form element or first element with error.
219      *
220      * @param string $name if this is set then the focus is forced to a field with this name
221      * @return string javascript to select form element with first error or
222      *                first element if no errors. Use this as a parameter
223      *                when calling print_header
224      */
225     function focus($name=NULL) {
226         $form =& $this->_form;
227         $elkeys = array_keys($form->_elementIndex);
228         $error = false;
229         if (isset($form->_errors) &&  0 != count($form->_errors)){
230             $errorkeys = array_keys($form->_errors);
231             $elkeys = array_intersect($elkeys, $errorkeys);
232             $error = true;
233         }
235         if ($error or empty($name)) {
236             $names = array();
237             while (empty($names) and !empty($elkeys)) {
238                 $el = array_shift($elkeys);
239                 $names = $form->_getElNamesRecursive($el);
240             }
241             if (!empty($names)) {
242                 $name = array_shift($names);
243             }
244         }
246         $focus = '';
247         if (!empty($name)) {
248             $focus = 'forms[\''.$form->getAttribute('id').'\'].elements[\''.$name.'\']';
249         }
251         return $focus;
252      }
254     /**
255      * Internal method. Alters submitted data to be suitable for quickforms processing.
256      * Must be called when the form is fully set up.
257      *
258      * @param string $method name of the method which alters submitted data
259      */
260     function _process_submission($method) {
261         $submission = array();
262         if ($method == 'post') {
263             if (!empty($_POST)) {
264                 $submission = $_POST;
265             }
266         } else {
267             $submission = array_merge_recursive($_GET, $_POST); // emulate handling of parameters in xxxx_param()
268         }
270         // following trick is needed to enable proper sesskey checks when using GET forms
271         // the _qf__.$this->_formname serves as a marker that form was actually submitted
272         if (array_key_exists('_qf__'.$this->_formname, $submission) and $submission['_qf__'.$this->_formname] == 1) {
273             if (!confirm_sesskey()) {
274                 print_error('invalidsesskey');
275             }
276             $files = $_FILES;
277         } else {
278             $submission = array();
279             $files = array();
280         }
281         $this->detectMissingSetType();
283         $this->_form->updateSubmission($submission, $files);
284     }
286     /**
287      * Internal method. Validates all old-style deprecated uploaded files.
288      * The new way is to upload files via repository api.
289      *
290      * @param array $files list of files to be validated
291      * @return bool|array Success or an array of errors
292      */
293     function _validate_files(&$files) {
294         global $CFG, $COURSE;
296         $files = array();
298         if (empty($_FILES)) {
299             // we do not need to do any checks because no files were submitted
300             // note: server side rules do not work for files - use custom verification in validate() instead
301             return true;
302         }
304         $errors = array();
305         $filenames = array();
307         // now check that we really want each file
308         foreach ($_FILES as $elname=>$file) {
309             $required = $this->_form->isElementRequired($elname);
311             if ($file['error'] == 4 and $file['size'] == 0) {
312                 if ($required) {
313                     $errors[$elname] = get_string('required');
314                 }
315                 unset($_FILES[$elname]);
316                 continue;
317             }
319             if (!empty($file['error'])) {
320                 $errors[$elname] = file_get_upload_error($file['error']);
321                 unset($_FILES[$elname]);
322                 continue;
323             }
325             if (!is_uploaded_file($file['tmp_name'])) {
326                 // TODO: improve error message
327                 $errors[$elname] = get_string('error');
328                 unset($_FILES[$elname]);
329                 continue;
330             }
332             if (!$this->_form->elementExists($elname) or !$this->_form->getElementType($elname)=='file') {
333                 // hmm, this file was not requested
334                 unset($_FILES[$elname]);
335                 continue;
336             }
338 /*
339   // TODO: rethink the file scanning MDL-19380
340             if ($CFG->runclamonupload) {
341                 if (!clam_scan_moodle_file($_FILES[$elname], $COURSE)) {
342                     $errors[$elname] = $_FILES[$elname]['uploadlog'];
343                     unset($_FILES[$elname]);
344                     continue;
345                 }
346             }
347 */
348             $filename = clean_param($_FILES[$elname]['name'], PARAM_FILE);
349             if ($filename === '') {
350                 // TODO: improve error message - wrong chars
351                 $errors[$elname] = get_string('error');
352                 unset($_FILES[$elname]);
353                 continue;
354             }
355             if (in_array($filename, $filenames)) {
356                 // TODO: improve error message - duplicate name
357                 $errors[$elname] = get_string('error');
358                 unset($_FILES[$elname]);
359                 continue;
360             }
361             $filenames[] = $filename;
362             $_FILES[$elname]['name'] = $filename;
364             $files[$elname] = $_FILES[$elname]['tmp_name'];
365         }
367         // return errors if found
368         if (count($errors) == 0){
369             return true;
371         } else {
372             $files = array();
373             return $errors;
374         }
375     }
377     /**
378      * Internal method. Validates filepicker and filemanager files if they are
379      * set as required fields. Also, sets the error message if encountered one.
380      *
381      * @return bool|array with errors
382      */
383     protected function validate_draft_files() {
384         global $USER;
385         $mform =& $this->_form;
387         $errors = array();
388         //Go through all the required elements and make sure you hit filepicker or
389         //filemanager element.
390         foreach ($mform->_rules as $elementname => $rules) {
391             $elementtype = $mform->getElementType($elementname);
392             //If element is of type filepicker then do validation
393             if (($elementtype == 'filepicker') || ($elementtype == 'filemanager')){
394                 //Check if rule defined is required rule
395                 foreach ($rules as $rule) {
396                     if ($rule['type'] == 'required') {
397                         $draftid = (int)$mform->getSubmitValue($elementname);
398                         $fs = get_file_storage();
399                         $context = context_user::instance($USER->id);
400                         if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
401                             $errors[$elementname] = $rule['message'];
402                         }
403                     }
404                 }
405             }
406         }
407         // Check all the filemanager elements to make sure they do not have too many
408         // files in them.
409         foreach ($mform->_elements as $element) {
410             if ($element->_type == 'filemanager') {
411                 $maxfiles = $element->getMaxfiles();
412                 if ($maxfiles > 0) {
413                     $draftid = (int)$element->getValue();
414                     $fs = get_file_storage();
415                     $context = context_user::instance($USER->id);
416                     $files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, '', false);
417                     if (count($files) > $maxfiles) {
418                         $errors[$element->getName()] = get_string('err_maxfiles', 'form', $maxfiles);
419                     }
420                 }
421             }
422         }
423         if (empty($errors)) {
424             return true;
425         } else {
426             return $errors;
427         }
428     }
430     /**
431      * Load in existing data as form defaults. Usually new entry defaults are stored directly in
432      * form definition (new entry form); this function is used to load in data where values
433      * already exist and data is being edited (edit entry form).
434      *
435      * note: $slashed param removed
436      *
437      * @param stdClass|array $default_values object or array of default values
438      */
439     function set_data($default_values) {
440         if (is_object($default_values)) {
441             $default_values = (array)$default_values;
442         }
443         $this->_form->setDefaults($default_values);
444     }
446     /**
447      * Check that form was submitted. Does not check validity of submitted data.
448      *
449      * @return bool true if form properly submitted
450      */
451     function is_submitted() {
452         return $this->_form->isSubmitted();
453     }
455     /**
456      * Checks if button pressed is not for submitting the form
457      *
458      * @staticvar bool $nosubmit keeps track of no submit button
459      * @return bool
460      */
461     function no_submit_button_pressed(){
462         static $nosubmit = null; // one check is enough
463         if (!is_null($nosubmit)){
464             return $nosubmit;
465         }
466         $mform =& $this->_form;
467         $nosubmit = false;
468         if (!$this->is_submitted()){
469             return false;
470         }
471         foreach ($mform->_noSubmitButtons as $nosubmitbutton){
472             if (optional_param($nosubmitbutton, 0, PARAM_RAW)){
473                 $nosubmit = true;
474                 break;
475             }
476         }
477         return $nosubmit;
478     }
481     /**
482      * Check that form data is valid.
483      * You should almost always use this, rather than {@link validate_defined_fields}
484      *
485      * @return bool true if form data valid
486      */
487     function is_validated() {
488         //finalize the form definition before any processing
489         if (!$this->_definition_finalized) {
490             $this->_definition_finalized = true;
491             $this->definition_after_data();
492         }
494         return $this->validate_defined_fields();
495     }
497     /**
498      * Validate the form.
499      *
500      * You almost always want to call {@link is_validated} instead of this
501      * because it calls {@link definition_after_data} first, before validating the form,
502      * which is what you want in 99% of cases.
503      *
504      * This is provided as a separate function for those special cases where
505      * you want the form validated before definition_after_data is called
506      * for example, to selectively add new elements depending on a no_submit_button press,
507      * but only when the form is valid when the no_submit_button is pressed,
508      *
509      * @param bool $validateonnosubmit optional, defaults to false.  The default behaviour
510      *             is NOT to validate the form when a no submit button has been pressed.
511      *             pass true here to override this behaviour
512      *
513      * @return bool true if form data valid
514      */
515     function validate_defined_fields($validateonnosubmit=false) {
516         static $validated = null; // one validation is enough
517         $mform =& $this->_form;
518         if ($this->no_submit_button_pressed() && empty($validateonnosubmit)){
519             return false;
520         } elseif ($validated === null) {
521             $internal_val = $mform->validate();
523             $files = array();
524             $file_val = $this->_validate_files($files);
525             //check draft files for validation and flag them if required files
526             //are not in draft area.
527             $draftfilevalue = $this->validate_draft_files();
529             if ($file_val !== true && $draftfilevalue !== true) {
530                 $file_val = array_merge($file_val, $draftfilevalue);
531             } else if ($draftfilevalue !== true) {
532                 $file_val = $draftfilevalue;
533             } //default is file_val, so no need to assign.
535             if ($file_val !== true) {
536                 if (!empty($file_val)) {
537                     foreach ($file_val as $element=>$msg) {
538                         $mform->setElementError($element, $msg);
539                     }
540                 }
541                 $file_val = false;
542             }
544             $data = $mform->exportValues();
545             $moodle_val = $this->validation($data, $files);
546             if ((is_array($moodle_val) && count($moodle_val)!==0)) {
547                 // non-empty array means errors
548                 foreach ($moodle_val as $element=>$msg) {
549                     $mform->setElementError($element, $msg);
550                 }
551                 $moodle_val = false;
553             } else {
554                 // anything else means validation ok
555                 $moodle_val = true;
556             }
558             $validated = ($internal_val and $moodle_val and $file_val);
559         }
560         return $validated;
561     }
563     /**
564      * Return true if a cancel button has been pressed resulting in the form being submitted.
565      *
566      * @return bool true if a cancel button has been pressed
567      */
568     function is_cancelled(){
569         $mform =& $this->_form;
570         if ($mform->isSubmitted()){
571             foreach ($mform->_cancelButtons as $cancelbutton){
572                 if (optional_param($cancelbutton, 0, PARAM_RAW)){
573                     return true;
574                 }
575             }
576         }
577         return false;
578     }
580     /**
581      * Return submitted data if properly submitted or returns NULL if validation fails or
582      * if there is no submitted data.
583      *
584      * note: $slashed param removed
585      *
586      * @return object submitted data; NULL if not valid or not submitted or cancelled
587      */
588     function get_data() {
589         $mform =& $this->_form;
591         if (!$this->is_cancelled() and $this->is_submitted() and $this->is_validated()) {
592             $data = $mform->exportValues();
593             unset($data['sesskey']); // we do not need to return sesskey
594             unset($data['_qf__'.$this->_formname]);   // we do not need the submission marker too
595             if (empty($data)) {
596                 return NULL;
597             } else {
598                 return (object)$data;
599             }
600         } else {
601             return NULL;
602         }
603     }
605     /**
606      * Return submitted data without validation or NULL if there is no submitted data.
607      * note: $slashed param removed
608      *
609      * @return object submitted data; NULL if not submitted
610      */
611     function get_submitted_data() {
612         $mform =& $this->_form;
614         if ($this->is_submitted()) {
615             $data = $mform->exportValues();
616             unset($data['sesskey']); // we do not need to return sesskey
617             unset($data['_qf__'.$this->_formname]);   // we do not need the submission marker too
618             if (empty($data)) {
619                 return NULL;
620             } else {
621                 return (object)$data;
622             }
623         } else {
624             return NULL;
625         }
626     }
628     /**
629      * Save verified uploaded files into directory. Upload process can be customised from definition()
630      *
631      * @deprecated since Moodle 2.0
632      * @todo MDL-31294 remove this api
633      * @see moodleform::save_stored_file()
634      * @see moodleform::save_file()
635      * @param string $destination path where file should be stored
636      * @return bool Always false
637      */
638     function save_files($destination) {
639         debugging('Not used anymore, please fix code! Use save_stored_file() or save_file() instead');
640         return false;
641     }
643     /**
644      * Returns name of uploaded file.
645      *
646      * @param string $elname first element if null
647      * @return string|bool false in case of failure, string if ok
648      */
649     function get_new_filename($elname=null) {
650         global $USER;
652         if (!$this->is_submitted() or !$this->is_validated()) {
653             return false;
654         }
656         if (is_null($elname)) {
657             if (empty($_FILES)) {
658                 return false;
659             }
660             reset($_FILES);
661             $elname = key($_FILES);
662         }
664         if (empty($elname)) {
665             return false;
666         }
668         $element = $this->_form->getElement($elname);
670         if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) {
671             $values = $this->_form->exportValues($elname);
672             if (empty($values[$elname])) {
673                 return false;
674             }
675             $draftid = $values[$elname];
676             $fs = get_file_storage();
677             $context = context_user::instance($USER->id);
678             if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
679                 return false;
680             }
681             $file = reset($files);
682             return $file->get_filename();
683         }
685         if (!isset($_FILES[$elname])) {
686             return false;
687         }
689         return $_FILES[$elname]['name'];
690     }
692     /**
693      * Save file to standard filesystem
694      *
695      * @param string $elname name of element
696      * @param string $pathname full path name of file
697      * @param bool $override override file if exists
698      * @return bool success
699      */
700     function save_file($elname, $pathname, $override=false) {
701         global $USER;
703         if (!$this->is_submitted() or !$this->is_validated()) {
704             return false;
705         }
706         if (file_exists($pathname)) {
707             if ($override) {
708                 if (!@unlink($pathname)) {
709                     return false;
710                 }
711             } else {
712                 return false;
713             }
714         }
716         $element = $this->_form->getElement($elname);
718         if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) {
719             $values = $this->_form->exportValues($elname);
720             if (empty($values[$elname])) {
721                 return false;
722             }
723             $draftid = $values[$elname];
724             $fs = get_file_storage();
725             $context = context_user::instance($USER->id);
726             if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
727                 return false;
728             }
729             $file = reset($files);
731             return $file->copy_content_to($pathname);
733         } else if (isset($_FILES[$elname])) {
734             return copy($_FILES[$elname]['tmp_name'], $pathname);
735         }
737         return false;
738     }
740     /**
741      * Returns a temporary file, do not forget to delete after not needed any more.
742      *
743      * @param string $elname name of the elmenet
744      * @return string|bool either string or false
745      */
746     function save_temp_file($elname) {
747         if (!$this->get_new_filename($elname)) {
748             return false;
749         }
750         if (!$dir = make_temp_directory('forms')) {
751             return false;
752         }
753         if (!$tempfile = tempnam($dir, 'tempup_')) {
754             return false;
755         }
756         if (!$this->save_file($elname, $tempfile, true)) {
757             // something went wrong
758             @unlink($tempfile);
759             return false;
760         }
762         return $tempfile;
763     }
765     /**
766      * Get draft files of a form element
767      * This is a protected method which will be used only inside moodleforms
768      *
769      * @param string $elname name of element
770      * @return array|bool|null
771      */
772     protected function get_draft_files($elname) {
773         global $USER;
775         if (!$this->is_submitted()) {
776             return false;
777         }
779         $element = $this->_form->getElement($elname);
781         if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) {
782             $values = $this->_form->exportValues($elname);
783             if (empty($values[$elname])) {
784                 return false;
785             }
786             $draftid = $values[$elname];
787             $fs = get_file_storage();
788             $context = context_user::instance($USER->id);
789             if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
790                 return null;
791             }
792             return $files;
793         }
794         return null;
795     }
797     /**
798      * Save file to local filesystem pool
799      *
800      * @param string $elname name of element
801      * @param int $newcontextid id of context
802      * @param string $newcomponent name of the component
803      * @param string $newfilearea name of file area
804      * @param int $newitemid item id
805      * @param string $newfilepath path of file where it get stored
806      * @param string $newfilename use specified filename, if not specified name of uploaded file used
807      * @param bool $overwrite overwrite file if exists
808      * @param int $newuserid new userid if required
809      * @return mixed stored_file object or false if error; may throw exception if duplicate found
810      */
811     function save_stored_file($elname, $newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath='/',
812                               $newfilename=null, $overwrite=false, $newuserid=null) {
813         global $USER;
815         if (!$this->is_submitted() or !$this->is_validated()) {
816             return false;
817         }
819         if (empty($newuserid)) {
820             $newuserid = $USER->id;
821         }
823         $element = $this->_form->getElement($elname);
824         $fs = get_file_storage();
826         if ($element instanceof MoodleQuickForm_filepicker) {
827             $values = $this->_form->exportValues($elname);
828             if (empty($values[$elname])) {
829                 return false;
830             }
831             $draftid = $values[$elname];
832             $context = context_user::instance($USER->id);
833             if (!$files = $fs->get_area_files($context->id, 'user' ,'draft', $draftid, 'id DESC', false)) {
834                 return false;
835             }
836             $file = reset($files);
837             if (is_null($newfilename)) {
838                 $newfilename = $file->get_filename();
839             }
841             if ($overwrite) {
842                 if ($oldfile = $fs->get_file($newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath, $newfilename)) {
843                     if (!$oldfile->delete()) {
844                         return false;
845                     }
846                 }
847             }
849             $file_record = array('contextid'=>$newcontextid, 'component'=>$newcomponent, 'filearea'=>$newfilearea, 'itemid'=>$newitemid,
850                                  'filepath'=>$newfilepath, 'filename'=>$newfilename, 'userid'=>$newuserid);
851             return $fs->create_file_from_storedfile($file_record, $file);
853         } else if (isset($_FILES[$elname])) {
854             $filename = is_null($newfilename) ? $_FILES[$elname]['name'] : $newfilename;
856             if ($overwrite) {
857                 if ($oldfile = $fs->get_file($newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath, $newfilename)) {
858                     if (!$oldfile->delete()) {
859                         return false;
860                     }
861                 }
862             }
864             $file_record = array('contextid'=>$newcontextid, 'component'=>$newcomponent, 'filearea'=>$newfilearea, 'itemid'=>$newitemid,
865                                  'filepath'=>$newfilepath, 'filename'=>$newfilename, 'userid'=>$newuserid);
866             return $fs->create_file_from_pathname($file_record, $_FILES[$elname]['tmp_name']);
867         }
869         return false;
870     }
872     /**
873      * Get content of uploaded file.
874      *
875      * @param string $elname name of file upload element
876      * @return string|bool false in case of failure, string if ok
877      */
878     function get_file_content($elname) {
879         global $USER;
881         if (!$this->is_submitted() or !$this->is_validated()) {
882             return false;
883         }
885         $element = $this->_form->getElement($elname);
887         if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) {
888             $values = $this->_form->exportValues($elname);
889             if (empty($values[$elname])) {
890                 return false;
891             }
892             $draftid = $values[$elname];
893             $fs = get_file_storage();
894             $context = context_user::instance($USER->id);
895             if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
896                 return false;
897             }
898             $file = reset($files);
900             return $file->get_content();
902         } else if (isset($_FILES[$elname])) {
903             return file_get_contents($_FILES[$elname]['tmp_name']);
904         }
906         return false;
907     }
909     /**
910      * Print html form.
911      */
912     function display() {
913         //finalize the form definition if not yet done
914         if (!$this->_definition_finalized) {
915             $this->_definition_finalized = true;
916             $this->definition_after_data();
917         }
919         $this->detectMissingSetType();
921         $this->_form->display();
922     }
924     /**
925      * Form definition. Abstract method - always override!
926      */
927     protected abstract function definition();
929     /**
930      * Dummy stub method - override if you need to setup the form depending on current
931      * values. This method is called after definition(), data submission and set_data().
932      * All form setup that is dependent on form values should go in here.
933      */
934     function definition_after_data(){
935     }
937     /**
938      * Dummy stub method - override if you needed to perform some extra validation.
939      * If there are errors return array of errors ("fieldname"=>"error message"),
940      * otherwise true if ok.
941      *
942      * Server side rules do not work for uploaded files, implement serverside rules here if needed.
943      *
944      * @param array $data array of ("fieldname"=>value) of submitted data
945      * @param array $files array of uploaded files "element_name"=>tmp_file_path
946      * @return array of "element_name"=>"error_description" if there are errors,
947      *         or an empty array if everything is OK (true allowed for backwards compatibility too).
948      */
949     function validation($data, $files) {
950         return array();
951     }
953     /**
954      * Helper used by {@link repeat_elements()}.
955      *
956      * @param int $i the index of this element.
957      * @param HTML_QuickForm_element $elementclone
958      * @param array $namecloned array of names
959      */
960     function repeat_elements_fix_clone($i, $elementclone, &$namecloned) {
961         $name = $elementclone->getName();
962         $namecloned[] = $name;
964         if (!empty($name)) {
965             $elementclone->setName($name."[$i]");
966         }
968         if (is_a($elementclone, 'HTML_QuickForm_header')) {
969             $value = $elementclone->_text;
970             $elementclone->setValue(str_replace('{no}', ($i+1), $value));
972         } else if (is_a($elementclone, 'HTML_QuickForm_submit') || is_a($elementclone, 'HTML_QuickForm_button')) {
973             $elementclone->setValue(str_replace('{no}', ($i+1), $elementclone->getValue()));
975         } else {
976             $value=$elementclone->getLabel();
977             $elementclone->setLabel(str_replace('{no}', ($i+1), $value));
978         }
979     }
981     /**
982      * Method to add a repeating group of elements to a form.
983      *
984      * @param array $elementobjs Array of elements or groups of elements that are to be repeated
985      * @param int $repeats no of times to repeat elements initially
986      * @param array $options Array of options to apply to elements. Array keys are element names.
987      *     This is an array of arrays. The second sets of keys are the option types for the elements :
988      *         'default' - default value is value
989      *         'type' - PARAM_* constant is value
990      *         'helpbutton' - helpbutton params array is value
991      *         'disabledif' - last three moodleform::disabledIf()
992      *         params are value as an array
993      * @param string $repeathiddenname name for hidden element storing no of repeats in this form
994      * @param string $addfieldsname name for button to add more fields
995      * @param int $addfieldsno how many fields to add at a time
996      * @param string $addstring name of button, {no} is replaced by no of blanks that will be added.
997      * @param bool $addbuttoninside if true, don't call closeHeaderBefore($addfieldsname). Default false.
998      * @return int no of repeats of element in this page
999      */
1000     function repeat_elements($elementobjs, $repeats, $options, $repeathiddenname,
1001             $addfieldsname, $addfieldsno=5, $addstring=null, $addbuttoninside=false){
1002         if ($addstring===null){
1003             $addstring = get_string('addfields', 'form', $addfieldsno);
1004         } else {
1005             $addstring = str_ireplace('{no}', $addfieldsno, $addstring);
1006         }
1007         $repeats = optional_param($repeathiddenname, $repeats, PARAM_INT);
1008         $addfields = optional_param($addfieldsname, '', PARAM_TEXT);
1009         if (!empty($addfields)){
1010             $repeats += $addfieldsno;
1011         }
1012         $mform =& $this->_form;
1013         $mform->registerNoSubmitButton($addfieldsname);
1014         $mform->addElement('hidden', $repeathiddenname, $repeats);
1015         $mform->setType($repeathiddenname, PARAM_INT);
1016         //value not to be overridden by submitted value
1017         $mform->setConstants(array($repeathiddenname=>$repeats));
1018         $namecloned = array();
1019         for ($i = 0; $i < $repeats; $i++) {
1020             foreach ($elementobjs as $elementobj){
1021                 $elementclone = fullclone($elementobj);
1022                 $this->repeat_elements_fix_clone($i, $elementclone, $namecloned);
1024                 if ($elementclone instanceof HTML_QuickForm_group && !$elementclone->_appendName) {
1025                     foreach ($elementclone->getElements() as $el) {
1026                         $this->repeat_elements_fix_clone($i, $el, $namecloned);
1027                     }
1028                     $elementclone->setLabel(str_replace('{no}', $i + 1, $elementclone->getLabel()));
1029                 }
1031                 $mform->addElement($elementclone);
1032             }
1033         }
1034         for ($i=0; $i<$repeats; $i++) {
1035             foreach ($options as $elementname => $elementoptions){
1036                 $pos=strpos($elementname, '[');
1037                 if ($pos!==FALSE){
1038                     $realelementname = substr($elementname, 0, $pos)."[$i]";
1039                     $realelementname .= substr($elementname, $pos);
1040                 }else {
1041                     $realelementname = $elementname."[$i]";
1042                 }
1043                 foreach ($elementoptions as  $option => $params){
1045                     switch ($option){
1046                         case 'default' :
1047                             $mform->setDefault($realelementname, str_replace('{no}', $i + 1, $params));
1048                             break;
1049                         case 'helpbutton' :
1050                             $params = array_merge(array($realelementname), $params);
1051                             call_user_func_array(array(&$mform, 'addHelpButton'), $params);
1052                             break;
1053                         case 'disabledif' :
1054                             foreach ($namecloned as $num => $name){
1055                                 if ($params[0] == $name){
1056                                     $params[0] = $params[0]."[$i]";
1057                                     break;
1058                                 }
1059                             }
1060                             $params = array_merge(array($realelementname), $params);
1061                             call_user_func_array(array(&$mform, 'disabledIf'), $params);
1062                             break;
1063                         case 'rule' :
1064                             if (is_string($params)){
1065                                 $params = array(null, $params, null, 'client');
1066                             }
1067                             $params = array_merge(array($realelementname), $params);
1068                             call_user_func_array(array(&$mform, 'addRule'), $params);
1069                             break;
1070                         case 'type' :
1071                             //Type should be set only once
1072                             if (!isset($mform->_types[$elementname])) {
1073                                 $mform->setType($elementname, $params);
1074                             }
1075                             break;
1077                         case 'expanded':
1078                             $mform->setExpanded($realelementname, $params);
1079                             break;
1081                         case 'advanced' :
1082                             $mform->setAdvanced($realelementname, $params);
1083                             break;
1084                     }
1085                 }
1086             }
1087         }
1088         $mform->addElement('submit', $addfieldsname, $addstring);
1090         if (!$addbuttoninside) {
1091             $mform->closeHeaderBefore($addfieldsname);
1092         }
1094         return $repeats;
1095     }
1097     /**
1098      * Adds a link/button that controls the checked state of a group of checkboxes.
1099      *
1100      * @param int $groupid The id of the group of advcheckboxes this element controls
1101      * @param string $text The text of the link. Defaults to selectallornone ("select all/none")
1102      * @param array $attributes associative array of HTML attributes
1103      * @param int $originalValue The original general state of the checkboxes before the user first clicks this element
1104      */
1105     function add_checkbox_controller($groupid, $text = null, $attributes = null, $originalValue = 0) {
1106         global $CFG, $PAGE;
1108         // Name of the controller button
1109         $checkboxcontrollername = 'nosubmit_checkbox_controller' . $groupid;
1110         $checkboxcontrollerparam = 'checkbox_controller'. $groupid;
1111         $checkboxgroupclass = 'checkboxgroup'.$groupid;
1113         // Set the default text if none was specified
1114         if (empty($text)) {
1115             $text = get_string('selectallornone', 'form');
1116         }
1118         $mform = $this->_form;
1119         $selectvalue = optional_param($checkboxcontrollerparam, null, PARAM_INT);
1120         $contollerbutton = optional_param($checkboxcontrollername, null, PARAM_ALPHAEXT);
1122         $newselectvalue = $selectvalue;
1123         if (is_null($selectvalue)) {
1124             $newselectvalue = $originalValue;
1125         } else if (!is_null($contollerbutton)) {
1126             $newselectvalue = (int) !$selectvalue;
1127         }
1128         // set checkbox state depending on orignal/submitted value by controoler button
1129         if (!is_null($contollerbutton) || is_null($selectvalue)) {
1130             foreach ($mform->_elements as $element) {
1131                 if (($element instanceof MoodleQuickForm_advcheckbox) &&
1132                         $element->getAttribute('class') == $checkboxgroupclass &&
1133                         !$element->isFrozen()) {
1134                     $mform->setConstants(array($element->getName() => $newselectvalue));
1135                 }
1136             }
1137         }
1139         $mform->addElement('hidden', $checkboxcontrollerparam, $newselectvalue, array('id' => "id_".$checkboxcontrollerparam));
1140         $mform->setType($checkboxcontrollerparam, PARAM_INT);
1141         $mform->setConstants(array($checkboxcontrollerparam => $newselectvalue));
1143         $PAGE->requires->yui_module('moodle-form-checkboxcontroller', 'M.form.checkboxcontroller',
1144                 array(
1145                     array('groupid' => $groupid,
1146                         'checkboxclass' => $checkboxgroupclass,
1147                         'checkboxcontroller' => $checkboxcontrollerparam,
1148                         'controllerbutton' => $checkboxcontrollername)
1149                     )
1150                 );
1152         require_once("$CFG->libdir/form/submit.php");
1153         $submitlink = new MoodleQuickForm_submit($checkboxcontrollername, $attributes);
1154         $mform->addElement($submitlink);
1155         $mform->registerNoSubmitButton($checkboxcontrollername);
1156         $mform->setDefault($checkboxcontrollername, $text);
1157     }
1159     /**
1160      * Use this method to a cancel and submit button to the end of your form. Pass a param of false
1161      * if you don't want a cancel button in your form. If you have a cancel button make sure you
1162      * check for it being pressed using is_cancelled() and redirecting if it is true before trying to
1163      * get data with get_data().
1164      *
1165      * @param bool $cancel whether to show cancel button, default true
1166      * @param string $submitlabel label for submit button, defaults to get_string('savechanges')
1167      */
1168     function add_action_buttons($cancel = true, $submitlabel=null){
1169         if (is_null($submitlabel)){
1170             $submitlabel = get_string('savechanges');
1171         }
1172         $mform =& $this->_form;
1173         if ($cancel){
1174             //when two elements we need a group
1175             $buttonarray=array();
1176             $buttonarray[] = &$mform->createElement('submit', 'submitbutton', $submitlabel);
1177             $buttonarray[] = &$mform->createElement('cancel');
1178             $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
1179             $mform->closeHeaderBefore('buttonar');
1180         } else {
1181             //no group needed
1182             $mform->addElement('submit', 'submitbutton', $submitlabel);
1183             $mform->closeHeaderBefore('submitbutton');
1184         }
1185     }
1187     /**
1188      * Adds an initialisation call for a standard JavaScript enhancement.
1189      *
1190      * This function is designed to add an initialisation call for a JavaScript
1191      * enhancement that should exist within javascript-static M.form.init_{enhancementname}.
1192      *
1193      * Current options:
1194      *  - Selectboxes
1195      *      - smartselect:  Turns a nbsp indented select box into a custom drop down
1196      *                      control that supports multilevel and category selection.
1197      *                      $enhancement = 'smartselect';
1198      *                      $options = array('selectablecategories' => true|false)
1199      *
1200      * @since Moodle 2.0
1201      * @param string|element $element form element for which Javascript needs to be initalized
1202      * @param string $enhancement which init function should be called
1203      * @param array $options options passed to javascript
1204      * @param array $strings strings for javascript
1205      */
1206     function init_javascript_enhancement($element, $enhancement, array $options=array(), array $strings=null) {
1207         global $PAGE;
1208         if (is_string($element)) {
1209             $element = $this->_form->getElement($element);
1210         }
1211         if (is_object($element)) {
1212             $element->_generateId();
1213             $elementid = $element->getAttribute('id');
1214             $PAGE->requires->js_init_call('M.form.init_'.$enhancement, array($elementid, $options));
1215             if (is_array($strings)) {
1216                 foreach ($strings as $string) {
1217                     if (is_array($string)) {
1218                         call_user_method_array('string_for_js', $PAGE->requires, $string);
1219                     } else {
1220                         $PAGE->requires->string_for_js($string, 'moodle');
1221                     }
1222                 }
1223             }
1224         }
1225     }
1227     /**
1228      * Returns a JS module definition for the mforms JS
1229      *
1230      * @return array
1231      */
1232     public static function get_js_module() {
1233         global $CFG;
1234         return array(
1235             'name' => 'mform',
1236             'fullpath' => '/lib/form/form.js',
1237             'requires' => array('base', 'node')
1238         );
1239     }
1241     /**
1242      * Detects elements with missing setType() declerations.
1243      *
1244      * Finds elements in the form which should a PARAM_ type set and throws a
1245      * developer debug warning for any elements without it. This is to reduce the
1246      * risk of potential security issues by developers mistakenly forgetting to set
1247      * the type.
1248      *
1249      * @return void
1250      */
1251     private function detectMissingSetType() {
1252         if (!debugging('', DEBUG_DEVELOPER)) {
1253             // Only for devs.
1254             return;
1255         }
1257         $mform = $this->_form;
1258         foreach ($mform->_elements as $element) {
1259             switch ($element->getType()) {
1260                 case 'hidden':
1261                 case 'text':
1262                 case 'url':
1263                     $key = $element->getName();
1264                     // For repeated elements we need to look for
1265                     // the "main" type, not for the one present
1266                     // on each repetition. All the stuff in formslib
1267                     // (repeat_elements(), updateSubmission()... seems
1268                     // to work that way.
1269                     $pos = strpos($key, '[');
1270                     if ($pos !== false) {
1271                         $key = substr($key, 0, $pos);
1272                     }
1273                     if (!array_key_exists($key, $mform->_types)) {
1274                         debugging("Did you remember to call setType() for '$key'? ".
1275                             'Defaulting to PARAM_RAW cleaning.', DEBUG_DEVELOPER);
1276                     }
1277                     break;
1278             }
1279         }
1280     }
1283 /**
1284  * MoodleQuickForm implementation
1285  *
1286  * You never extend this class directly. The class methods of this class are available from
1287  * the private $this->_form property on moodleform and its children. You generally only
1288  * call methods on this class from within abstract methods that you override on moodleform such
1289  * as definition and definition_after_data
1290  *
1291  * @package   core_form
1292  * @category  form
1293  * @copyright 2006 Jamie Pratt <me@jamiep.org>
1294  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1295  */
1296 class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
1297     /** @var array type (PARAM_INT, PARAM_TEXT etc) of element value */
1298     var $_types = array();
1300     /** @var array dependent state for the element/'s */
1301     var $_dependencies = array();
1303     /** @var array Array of buttons that if pressed do not result in the processing of the form. */
1304     var $_noSubmitButtons=array();
1306     /** @var array Array of buttons that if pressed do not result in the processing of the form. */
1307     var $_cancelButtons=array();
1309     /** @var array Array whose keys are element names. If the key exists this is a advanced element */
1310     var $_advancedElements = array();
1312     /**
1313      * Array whose keys are element names and values are the desired collapsible state.
1314      * True for collapsed, False for expanded. If not present, set to default in
1315      * {@link self::accept()}.
1316      *
1317      * @var array
1318      */
1319     var $_collapsibleElements = array();
1321     /**
1322      * Whether to enable shortforms for this form
1323      *
1324      * @var boolean
1325      */
1326     var $_disableShortforms = false;
1328     /** @var bool whether to automatically initialise M.formchangechecker for this form. */
1329     protected $_use_form_change_checker = true;
1331     /**
1332      * The form name is derived from the class name of the wrapper minus the trailing form
1333      * It is a name with words joined by underscores whereas the id attribute is words joined by underscores.
1334      * @var string
1335      */
1336     var $_formName = '';
1338     /**
1339      * String with the html for hidden params passed in as part of a moodle_url
1340      * object for the action. Output in the form.
1341      * @var string
1342      */
1343     var $_pageparams = '';
1345     /**
1346      * Class constructor - same parameters as HTML_QuickForm_DHTMLRulesTableless
1347      *
1348      * @staticvar int $formcounter counts number of forms
1349      * @param string $formName Form's name.
1350      * @param string $method Form's method defaults to 'POST'
1351      * @param string|moodle_url $action Form's action
1352      * @param string $target (optional)Form's target defaults to none
1353      * @param mixed $attributes (optional)Extra attributes for <form> tag
1354      */
1355     function MoodleQuickForm($formName, $method, $action, $target='', $attributes=null){
1356         global $CFG, $OUTPUT;
1358         static $formcounter = 1;
1360         HTML_Common::HTML_Common($attributes);
1361         $target = empty($target) ? array() : array('target' => $target);
1362         $this->_formName = $formName;
1363         if (is_a($action, 'moodle_url')){
1364             $this->_pageparams = html_writer::input_hidden_params($action);
1365             $action = $action->out_omit_querystring();
1366         } else {
1367             $this->_pageparams = '';
1368         }
1369         // No 'name' atttribute for form in xhtml strict :
1370         $attributes = array('action' => $action, 'method' => $method, 'accept-charset' => 'utf-8') + $target;
1371         if (is_null($this->getAttribute('id'))) {
1372             $attributes['id'] = 'mform' . $formcounter;
1373         }
1374         $formcounter++;
1375         $this->updateAttributes($attributes);
1377         // This is custom stuff for Moodle :
1378         $oldclass=   $this->getAttribute('class');
1379         if (!empty($oldclass)){
1380             $this->updateAttributes(array('class'=>$oldclass.' mform'));
1381         }else {
1382             $this->updateAttributes(array('class'=>'mform'));
1383         }
1384         $this->_reqHTML = '<img class="req" title="'.get_string('requiredelement', 'form').'" alt="'.get_string('requiredelement', 'form').'" src="'.$OUTPUT->pix_url('req') .'" />';
1385         $this->_advancedHTML = '<img class="adv" title="'.get_string('advancedelement', 'form').'" alt="'.get_string('advancedelement', 'form').'" src="'.$OUTPUT->pix_url('adv') .'" />';
1386         $this->setRequiredNote(get_string('somefieldsrequired', 'form', '<img alt="'.get_string('requiredelement', 'form').'" src="'.$OUTPUT->pix_url('req') .'" />'));
1387     }
1389     /**
1390      * Use this method to indicate an element in a form is an advanced field. If items in a form
1391      * are marked as advanced then 'Hide/Show Advanced' buttons will automatically be displayed in the
1392      * form so the user can decide whether to display advanced form controls.
1393      *
1394      * If you set a header element to advanced then all elements it contains will also be set as advanced.
1395      *
1396      * @param string $elementName group or element name (not the element name of something inside a group).
1397      * @param bool $advanced default true sets the element to advanced. False removes advanced mark.
1398      */
1399     function setAdvanced($elementName, $advanced = true) {
1400         if ($advanced){
1401             $this->_advancedElements[$elementName]='';
1402         } elseif (isset($this->_advancedElements[$elementName])) {
1403             unset($this->_advancedElements[$elementName]);
1404         }
1405     }
1407     /**
1408      * Use this method to indicate that the fieldset should be shown as expanded.
1409      * The method is applicable to header elements only.
1410      *
1411      * @param string $headername header element name
1412      * @param boolean $expanded default true sets the element to expanded. False makes the element collapsed.
1413      * @param boolean $ignoreuserstate override the state regardless of the state it was on when
1414      *                                 the form was submitted.
1415      * @return void
1416      */
1417     function setExpanded($headername, $expanded = true, $ignoreuserstate = false) {
1418         if (empty($headername)) {
1419             return;
1420         }
1421         $element = $this->getElement($headername);
1422         if ($element->getType() != 'header') {
1423             debugging('Cannot use setExpanded on non-header elements', DEBUG_DEVELOPER);
1424             return;
1425         }
1426         if (!$headerid = $element->getAttribute('id')) {
1427             $element->_generateId();
1428             $headerid = $element->getAttribute('id');
1429         }
1430         if ($this->getElementType('mform_isexpanded_' . $headerid) === false) {
1431             // See if the form has been submitted already.
1432             $formexpanded = optional_param('mform_isexpanded_' . $headerid, -1, PARAM_INT);
1433             if (!$ignoreuserstate && $formexpanded != -1) {
1434                 // Override expanded state with the form variable.
1435                 $expanded = $formexpanded;
1436             }
1437             // Create the form element for storing expanded state.
1438             $this->addElement('hidden', 'mform_isexpanded_' . $headerid);
1439             $this->setType('mform_isexpanded_' . $headerid, PARAM_INT);
1440             $this->setConstant('mform_isexpanded_' . $headerid, (int) $expanded);
1441         }
1442         $this->_collapsibleElements[$headername] = !$expanded;
1443     }
1445     /**
1446      * Use this method to add show more/less status element required for passing
1447      * over the advanced elements visibility status on the form submission.
1448      *
1449      * @param string $headerName header element name.
1450      * @param boolean $showmore default false sets the advanced elements to be hidden.
1451      */
1452     function addAdvancedStatusElement($headerid, $showmore=false){
1453         // Add extra hidden element to store advanced items state for each section.
1454         if ($this->getElementType('mform_showmore_' . $headerid) === false) {
1455             // See if we the form has been submitted already.
1456             $formshowmore = optional_param('mform_showmore_' . $headerid, -1, PARAM_INT);
1457             if (!$showmore && $formshowmore != -1) {
1458                 // Override showmore state with the form variable.
1459                 $showmore = $formshowmore;
1460             }
1461             // Create the form element for storing advanced items state.
1462             $this->addElement('hidden', 'mform_showmore_' . $headerid);
1463             $this->setType('mform_showmore_' . $headerid, PARAM_INT);
1464             $this->setConstant('mform_showmore_' . $headerid, (int)$showmore);
1465         }
1466     }
1468     /**
1469      * This function has been deprecated. Show advanced has been replaced by
1470      * "Show more.../Show less..." in the shortforms javascript module.
1471      *
1472      * @deprecated since Moodle 2.5
1473      * @param bool $showadvancedNow if true will show advanced elements.
1474       */
1475     function setShowAdvanced($showadvancedNow = null){
1476         debugging('Call to deprecated function setShowAdvanced. See "Show more.../Show less..." in shortforms yui module.');
1477     }
1479     /**
1480      * This function has been deprecated. Show advanced has been replaced by
1481      * "Show more.../Show less..." in the shortforms javascript module.
1482      *
1483      * @deprecated since Moodle 2.5
1484      * @return bool (Always false)
1485       */
1486     function getShowAdvanced(){
1487         debugging('Call to deprecated function setShowAdvanced. See "Show more.../Show less..." in shortforms yui module.');
1488         return false;
1489     }
1491     /**
1492      * Use this method to indicate that the form will not be using shortforms.
1493      *
1494      * @param boolean $disable default true, controls if the shortforms are disabled.
1495      */
1496     function setDisableShortforms ($disable = true) {
1497         $this->_disableShortforms = $disable;
1498     }
1500     /**
1501      * Call this method if you don't want the formchangechecker JavaScript to be
1502      * automatically initialised for this form.
1503      */
1504     public function disable_form_change_checker() {
1505         $this->_use_form_change_checker = false;
1506     }
1508     /**
1509      * If you have called {@link disable_form_change_checker()} then you can use
1510      * this method to re-enable it. It is enabled by default, so normally you don't
1511      * need to call this.
1512      */
1513     public function enable_form_change_checker() {
1514         $this->_use_form_change_checker = true;
1515     }
1517     /**
1518      * @return bool whether this form should automatically initialise
1519      *      formchangechecker for itself.
1520      */
1521     public function is_form_change_checker_enabled() {
1522         return $this->_use_form_change_checker;
1523     }
1525     /**
1526     * Accepts a renderer
1527     *
1528     * @param HTML_QuickForm_Renderer $renderer An HTML_QuickForm_Renderer object
1529     */
1530     function accept(&$renderer) {
1531         if (method_exists($renderer, 'setAdvancedElements')){
1532             //Check for visible fieldsets where all elements are advanced
1533             //and mark these headers as advanced as well.
1534             //Also mark all elements in a advanced header as advanced.
1535             $stopFields = $renderer->getStopFieldSetElements();
1536             $lastHeader = null;
1537             $lastHeaderAdvanced = false;
1538             $anyAdvanced = false;
1539             $anyError = false;
1540             foreach (array_keys($this->_elements) as $elementIndex){
1541                 $element =& $this->_elements[$elementIndex];
1543                 // if closing header and any contained element was advanced then mark it as advanced
1544                 if ($element->getType()=='header' || in_array($element->getName(), $stopFields)){
1545                     if ($anyAdvanced && !is_null($lastHeader)) {
1546                         $lastHeader->_generateId();
1547                         $this->setAdvanced($lastHeader->getName());
1548                         $this->addAdvancedStatusElement($lastHeader->getAttribute('id'), $anyError);
1549                     }
1550                     $lastHeaderAdvanced = false;
1551                     unset($lastHeader);
1552                     $lastHeader = null;
1553                 } elseif ($lastHeaderAdvanced) {
1554                     $this->setAdvanced($element->getName());
1555                 }
1557                 if ($element->getType()=='header'){
1558                     $lastHeader =& $element;
1559                     $anyAdvanced = false;
1560                     $anyError = false;
1561                     $lastHeaderAdvanced = isset($this->_advancedElements[$element->getName()]);
1562                 } elseif (isset($this->_advancedElements[$element->getName()])){
1563                     $anyAdvanced = true;
1564                     if (isset($this->_errors[$element->getName()])) {
1565                         $anyError = true;
1566                     }
1567                 }
1568             }
1569             // the last header may not be closed yet...
1570             if ($anyAdvanced && !is_null($lastHeader)){
1571                 $this->setAdvanced($lastHeader->getName());
1572                 $lastHeader->_generateId();
1573                 $this->addAdvancedStatusElement($lastHeader->getAttribute('id'), $anyError);
1574             }
1575             $renderer->setAdvancedElements($this->_advancedElements);
1576         }
1577         if (method_exists($renderer, 'setCollapsibleElements') && !$this->_disableShortforms) {
1579             // Count the number of sections.
1580             $headerscount = 0;
1581             foreach (array_keys($this->_elements) as $elementIndex){
1582                 $element =& $this->_elements[$elementIndex];
1583                 if ($element->getType() == 'header') {
1584                     $headerscount++;
1585                 }
1586             }
1588             $anyrequiredorerror = false;
1589             $headercounter = 0;
1590             $headername = null;
1591             foreach (array_keys($this->_elements) as $elementIndex){
1592                 $element =& $this->_elements[$elementIndex];
1594                 if ($element->getType() == 'header') {
1595                     $headercounter++;
1596                     $element->_generateId();
1597                     $headername = $element->getName();
1598                     $anyrequiredorerror = false;
1599                 } else if (in_array($element->getName(), $this->_required) || isset($this->_errors[$element->getName()])) {
1600                     $anyrequiredorerror = true;
1601                 } else {
1602                     // Do not reset $anyrequiredorerror to false because we do not want any other element
1603                     // in this header (fieldset) to possibly revert the state given.
1604                 }
1606                 if ($element->getType() == 'header') {
1607                     if ($headercounter === 1 && !isset($this->_collapsibleElements[$headername])) {
1608                         // By default the first section is always expanded, except if a state has already been set.
1609                         $this->setExpanded($headername, true);
1610                     } else if (($headercounter === 2 && $headerscount === 2) && !isset($this->_collapsibleElements[$headername])) {
1611                         // The second section is always expanded if the form only contains 2 sections),
1612                         // except if a state has already been set.
1613                         $this->setExpanded($headername, true);
1614                     }
1615                 } else if ($anyrequiredorerror) {
1616                     // If any error or required field are present within the header, we need to expand it.
1617                     $this->setExpanded($headername, true, true);
1618                 } else if (!isset($this->_collapsibleElements[$headername])) {
1619                     // Define element as collapsed by default.
1620                     $this->setExpanded($headername, false);
1621                 }
1622             }
1624             // Pass the array to renderer object.
1625             $renderer->setCollapsibleElements($this->_collapsibleElements);
1626         }
1627         parent::accept($renderer);
1628     }
1630     /**
1631      * Adds one or more element names that indicate the end of a fieldset
1632      *
1633      * @param string $elementName name of the element
1634      */
1635     function closeHeaderBefore($elementName){
1636         $renderer =& $this->defaultRenderer();
1637         $renderer->addStopFieldsetElements($elementName);
1638     }
1640     /**
1641      * Should be used for all elements of a form except for select, radio and checkboxes which
1642      * clean their own data.
1643      *
1644      * @param string $elementname
1645      * @param int $paramtype defines type of data contained in element. Use the constants PARAM_*.
1646      *        {@link lib/moodlelib.php} for defined parameter types
1647      */
1648     function setType($elementname, $paramtype) {
1649         $this->_types[$elementname] = $paramtype;
1650     }
1652     /**
1653      * This can be used to set several types at once.
1654      *
1655      * @param array $paramtypes types of parameters.
1656      * @see MoodleQuickForm::setType
1657      */
1658     function setTypes($paramtypes) {
1659         $this->_types = $paramtypes + $this->_types;
1660     }
1662     /**
1663      * Updates submitted values
1664      *
1665      * @param array $submission submitted values
1666      * @param array $files list of files
1667      */
1668     function updateSubmission($submission, $files) {
1669         $this->_flagSubmitted = false;
1671         if (empty($submission)) {
1672             $this->_submitValues = array();
1673         } else {
1674             foreach ($submission as $key=>$s) {
1675                 if (array_key_exists($key, $this->_types)) {
1676                     $type = $this->_types[$key];
1677                 } else {
1678                     $type = PARAM_RAW;
1679                 }
1680                 if (is_array($s)) {
1681                     $submission[$key] = clean_param_array($s, $type, true);
1682                 } else {
1683                     $submission[$key] = clean_param($s, $type);
1684                 }
1685             }
1686             $this->_submitValues = $submission;
1687             $this->_flagSubmitted = true;
1688         }
1690         if (empty($files)) {
1691             $this->_submitFiles = array();
1692         } else {
1693             $this->_submitFiles = $files;
1694             $this->_flagSubmitted = true;
1695         }
1697         // need to tell all elements that they need to update their value attribute.
1698          foreach (array_keys($this->_elements) as $key) {
1699              $this->_elements[$key]->onQuickFormEvent('updateValue', null, $this);
1700          }
1701     }
1703     /**
1704      * Returns HTML for required elements
1705      *
1706      * @return string
1707      */
1708     function getReqHTML(){
1709         return $this->_reqHTML;
1710     }
1712     /**
1713      * Returns HTML for advanced elements
1714      *
1715      * @return string
1716      */
1717     function getAdvancedHTML(){
1718         return $this->_advancedHTML;
1719     }
1721     /**
1722      * Initializes a default form value. Used to specify the default for a new entry where
1723      * no data is loaded in using moodleform::set_data()
1724      *
1725      * note: $slashed param removed
1726      *
1727      * @param string $elementName element name
1728      * @param mixed $defaultValue values for that element name
1729      */
1730     function setDefault($elementName, $defaultValue){
1731         $this->setDefaults(array($elementName=>$defaultValue));
1732     }
1734     /**
1735      * Add a help button to element, only one button per element is allowed.
1736      *
1737      * This is new, simplified and preferable method of setting a help icon on form elements.
1738      * It uses the new $OUTPUT->help_icon().
1739      *
1740      * Typically, you will provide the same identifier and the component as you have used for the
1741      * label of the element. The string identifier with the _help suffix added is then used
1742      * as the help string.
1743      *
1744      * There has to be two strings defined:
1745      *   1/ get_string($identifier, $component) - the title of the help page
1746      *   2/ get_string($identifier.'_help', $component) - the actual help page text
1747      *
1748      * @since Moodle 2.0
1749      * @param string $elementname name of the element to add the item to
1750      * @param string $identifier help string identifier without _help suffix
1751      * @param string $component component name to look the help string in
1752      * @param string $linktext optional text to display next to the icon
1753      * @param bool $suppresscheck set to true if the element may not exist
1754      */
1755     function addHelpButton($elementname, $identifier, $component = 'moodle', $linktext = '', $suppresscheck = false) {
1756         global $OUTPUT;
1757         if (array_key_exists($elementname, $this->_elementIndex)) {
1758             $element = $this->_elements[$this->_elementIndex[$elementname]];
1759             $element->_helpbutton = $OUTPUT->help_icon($identifier, $component, $linktext);
1760         } else if (!$suppresscheck) {
1761             debugging(get_string('nonexistentformelements', 'form', $elementname));
1762         }
1763     }
1765     /**
1766      * Set constant value not overridden by _POST or _GET
1767      * note: this does not work for complex names with [] :-(
1768      *
1769      * @param string $elname name of element
1770      * @param mixed $value
1771      */
1772     function setConstant($elname, $value) {
1773         $this->_constantValues = HTML_QuickForm::arrayMerge($this->_constantValues, array($elname=>$value));
1774         $element =& $this->getElement($elname);
1775         $element->onQuickFormEvent('updateValue', null, $this);
1776     }
1778     /**
1779      * export submitted values
1780      *
1781      * @param string $elementList list of elements in form
1782      * @return array
1783      */
1784     function exportValues($elementList = null){
1785         $unfiltered = array();
1786         if (null === $elementList) {
1787             // iterate over all elements, calling their exportValue() methods
1788             foreach (array_keys($this->_elements) as $key) {
1789                 if ($this->_elements[$key]->isFrozen() && !$this->_elements[$key]->_persistantFreeze) {
1790                     $varname = $this->_elements[$key]->_attributes['name'];
1791                     $value = '';
1792                     // If we have a default value then export it.
1793                     if (isset($this->_defaultValues[$varname])) {
1794                         $value = $this->prepare_fixed_value($varname, $this->_defaultValues[$varname]);
1795                     }
1796                 } else {
1797                     $value = $this->_elements[$key]->exportValue($this->_submitValues, true);
1798                 }
1800                 if (is_array($value)) {
1801                     // This shit throws a bogus warning in PHP 4.3.x
1802                     $unfiltered = HTML_QuickForm::arrayMerge($unfiltered, $value);
1803                 }
1804             }
1805         } else {
1806             if (!is_array($elementList)) {
1807                 $elementList = array_map('trim', explode(',', $elementList));
1808             }
1809             foreach ($elementList as $elementName) {
1810                 $value = $this->exportValue($elementName);
1811                 if (@PEAR::isError($value)) {
1812                     return $value;
1813                 }
1814                 //oh, stock QuickFOrm was returning array of arrays!
1815                 $unfiltered = HTML_QuickForm::arrayMerge($unfiltered, $value);
1816             }
1817         }
1819         if (is_array($this->_constantValues)) {
1820             $unfiltered = HTML_QuickForm::arrayMerge($unfiltered, $this->_constantValues);
1821         }
1822         return $unfiltered;
1823     }
1825     /**
1826      * This is a bit of a hack, and it duplicates the code in
1827      * HTML_QuickForm_element::_prepareValue, but I could not think of a way or
1828      * reliably calling that code. (Think about date selectors, for example.)
1829      * @param string $name the element name.
1830      * @param mixed $value the fixed value to set.
1831      * @return mixed the appropriate array to add to the $unfiltered array.
1832      */
1833     protected function prepare_fixed_value($name, $value) {
1834         if (null === $value) {
1835             return null;
1836         } else {
1837             if (!strpos($name, '[')) {
1838                 return array($name => $value);
1839             } else {
1840                 $valueAry = array();
1841                 $myIndex  = "['" . str_replace(array(']', '['), array('', "']['"), $name) . "']";
1842                 eval("\$valueAry$myIndex = \$value;");
1843                 return $valueAry;
1844             }
1845         }
1846     }
1848     /**
1849      * Adds a validation rule for the given field
1850      *
1851      * If the element is in fact a group, it will be considered as a whole.
1852      * To validate grouped elements as separated entities,
1853      * use addGroupRule instead of addRule.
1854      *
1855      * @param string $element Form element name
1856      * @param string $message Message to display for invalid data
1857      * @param string $type Rule type, use getRegisteredRules() to get types
1858      * @param string $format (optional)Required for extra rule data
1859      * @param string $validation (optional)Where to perform validation: "server", "client"
1860      * @param bool $reset Client-side validation: reset the form element to its original value if there is an error?
1861      * @param bool $force Force the rule to be applied, even if the target form element does not exist
1862      */
1863     function addRule($element, $message, $type, $format=null, $validation='server', $reset = false, $force = false)
1864     {
1865         parent::addRule($element, $message, $type, $format, $validation, $reset, $force);
1866         if ($validation == 'client') {
1867             $this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_formName . '; } catch(e) { return true; } return myValidator(this);'));
1868         }
1870     }
1872     /**
1873      * Adds a validation rule for the given group of elements
1874      *
1875      * Only groups with a name can be assigned a validation rule
1876      * Use addGroupRule when you need to validate elements inside the group.
1877      * Use addRule if you need to validate the group as a whole. In this case,
1878      * the same rule will be applied to all elements in the group.
1879      * Use addRule if you need to validate the group against a function.
1880      *
1881      * @param string $group Form group name
1882      * @param array|string $arg1 Array for multiple elements or error message string for one element
1883      * @param string $type (optional)Rule type use getRegisteredRules() to get types
1884      * @param string $format (optional)Required for extra rule data
1885      * @param int $howmany (optional)How many valid elements should be in the group
1886      * @param string $validation (optional)Where to perform validation: "server", "client"
1887      * @param bool $reset Client-side: whether to reset the element's value to its original state if validation failed.
1888      */
1889     function addGroupRule($group, $arg1, $type='', $format=null, $howmany=0, $validation = 'server', $reset = false)
1890     {
1891         parent::addGroupRule($group, $arg1, $type, $format, $howmany, $validation, $reset);
1892         if (is_array($arg1)) {
1893              foreach ($arg1 as $rules) {
1894                 foreach ($rules as $rule) {
1895                     $validation = (isset($rule[3]) && 'client' == $rule[3])? 'client': 'server';
1897                     if ('client' == $validation) {
1898                         $this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_formName . '; } catch(e) { return true; } return myValidator(this);'));
1899                     }
1900                 }
1901             }
1902         } elseif (is_string($arg1)) {
1904             if ($validation == 'client') {
1905                 $this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_formName . '; } catch(e) { return true; } return myValidator(this);'));
1906             }
1907         }
1908     }
1910     /**
1911      * Returns the client side validation script
1912      *
1913      * The code here was copied from HTML_QuickForm_DHTMLRulesTableless who copied it from  HTML_QuickForm
1914      * and slightly modified to run rules per-element
1915      * Needed to override this because of an error with client side validation of grouped elements.
1916      *
1917      * @return string Javascript to perform validation, empty string if no 'client' rules were added
1918      */
1919     function getValidationScript()
1920     {
1921         if (empty($this->_rules) || empty($this->_attributes['onsubmit'])) {
1922             return '';
1923         }
1925         include_once('HTML/QuickForm/RuleRegistry.php');
1926         $registry =& HTML_QuickForm_RuleRegistry::singleton();
1927         $test = array();
1928         $js_escape = array(
1929             "\r"    => '\r',
1930             "\n"    => '\n',
1931             "\t"    => '\t',
1932             "'"     => "\\'",
1933             '"'     => '\"',
1934             '\\'    => '\\\\'
1935         );
1937         foreach ($this->_rules as $elementName => $rules) {
1938             foreach ($rules as $rule) {
1939                 if ('client' == $rule['validation']) {
1940                     unset($element); //TODO: find out how to properly initialize it
1942                     $dependent  = isset($rule['dependent']) && is_array($rule['dependent']);
1943                     $rule['message'] = strtr($rule['message'], $js_escape);
1945                     if (isset($rule['group'])) {
1946                         $group    =& $this->getElement($rule['group']);
1947                         // No JavaScript validation for frozen elements
1948                         if ($group->isFrozen()) {
1949                             continue 2;
1950                         }
1951                         $elements =& $group->getElements();
1952                         foreach (array_keys($elements) as $key) {
1953                             if ($elementName == $group->getElementName($key)) {
1954                                 $element =& $elements[$key];
1955                                 break;
1956                             }
1957                         }
1958                     } elseif ($dependent) {
1959                         $element   =  array();
1960                         $element[] =& $this->getElement($elementName);
1961                         foreach ($rule['dependent'] as $elName) {
1962                             $element[] =& $this->getElement($elName);
1963                         }
1964                     } else {
1965                         $element =& $this->getElement($elementName);
1966                     }
1967                     // No JavaScript validation for frozen elements
1968                     if (is_object($element) && $element->isFrozen()) {
1969                         continue 2;
1970                     } elseif (is_array($element)) {
1971                         foreach (array_keys($element) as $key) {
1972                             if ($element[$key]->isFrozen()) {
1973                                 continue 3;
1974                             }
1975                         }
1976                     }
1977                     //for editor element, [text] is appended to the name.
1978                     if ($element->getType() == 'editor') {
1979                         $elementName .= '[text]';
1980                         //Add format to rule as moodleform check which format is supported by browser
1981                         //it is not set anywhere... So small hack to make sure we pass it down to quickform
1982                         if (is_null($rule['format'])) {
1983                             $rule['format'] = $element->getFormat();
1984                         }
1985                     }
1986                     // Fix for bug displaying errors for elements in a group
1987                     $test[$elementName][0][] = $registry->getValidationScript($element, $elementName, $rule);
1988                     $test[$elementName][1]=$element;
1989                     //end of fix
1990                 }
1991             }
1992         }
1994         // Fix for MDL-9524. If you don't do this, then $element may be left as a reference to one of the fields in
1995         // the form, and then that form field gets corrupted by the code that follows.
1996         unset($element);
1998         $js = '
1999 <script type="text/javascript">
2000 //<![CDATA[
2002 var skipClientValidation = false;
2004 function qf_errorHandler(element, _qfMsg) {
2005   div = element.parentNode;
2007   if ((div == undefined) || (element.name == undefined)) {
2008     //no checking can be done for undefined elements so let server handle it.
2009     return true;
2010   }
2012   if (_qfMsg != \'\') {
2013     var errorSpan = document.getElementById(\'id_error_\'+element.name);
2014     if (!errorSpan) {
2015       errorSpan = document.createElement("span");
2016       errorSpan.id = \'id_error_\'+element.name;
2017       errorSpan.className = "error";
2018       element.parentNode.insertBefore(errorSpan, element.parentNode.firstChild);
2019     }
2021     while (errorSpan.firstChild) {
2022       errorSpan.removeChild(errorSpan.firstChild);
2023     }
2025     errorSpan.appendChild(document.createTextNode(_qfMsg.substring(3)));
2026     errorSpan.appendChild(document.createElement("br"));
2028     if (div.className.substr(div.className.length - 6, 6) != " error"
2029         && div.className != "error") {
2030       div.className += " error";
2031     }
2033     return false;
2034   } else {
2035     var errorSpan = document.getElementById(\'id_error_\'+element.name);
2036     if (errorSpan) {
2037       errorSpan.parentNode.removeChild(errorSpan);
2038     }
2040     if (div.className.substr(div.className.length - 6, 6) == " error") {
2041       div.className = div.className.substr(0, div.className.length - 6);
2042     } else if (div.className == "error") {
2043       div.className = "";
2044     }
2046     return true;
2047   }
2048 }';
2049         $validateJS = '';
2050         foreach ($test as $elementName => $jsandelement) {
2051             // Fix for bug displaying errors for elements in a group
2052             //unset($element);
2053             list($jsArr,$element)=$jsandelement;
2054             //end of fix
2055             $escapedElementName = preg_replace_callback(
2056                 '/[_\[\]-]/',
2057                 create_function('$matches', 'return sprintf("_%2x",ord($matches[0]));'),
2058                 $elementName);
2059             $js .= '
2060 function validate_' . $this->_formName . '_' . $escapedElementName . '(element) {
2061   if (undefined == element) {
2062      //required element was not found, then let form be submitted without client side validation
2063      return true;
2064   }
2065   var value = \'\';
2066   var errFlag = new Array();
2067   var _qfGroups = {};
2068   var _qfMsg = \'\';
2069   var frm = element.parentNode;
2070   if ((undefined != element.name) && (frm != undefined)) {
2071       while (frm && frm.nodeName.toUpperCase() != "FORM") {
2072         frm = frm.parentNode;
2073       }
2074     ' . join("\n", $jsArr) . '
2075       return qf_errorHandler(element, _qfMsg);
2076   } else {
2077     //element name should be defined else error msg will not be displayed.
2078     return true;
2079   }
2081 ';
2082             $validateJS .= '
2083   ret = validate_' . $this->_formName . '_' . $escapedElementName.'(frm.elements[\''.$elementName.'\']) && ret;
2084   if (!ret && !first_focus) {
2085     first_focus = true;
2086     frm.elements[\''.$elementName.'\'].focus();
2087   }
2088 ';
2090             // Fix for bug displaying errors for elements in a group
2091             //unset($element);
2092             //$element =& $this->getElement($elementName);
2093             //end of fix
2094             $valFunc = 'validate_' . $this->_formName . '_' . $escapedElementName . '(this)';
2095             $onBlur = $element->getAttribute('onBlur');
2096             $onChange = $element->getAttribute('onChange');
2097             $element->updateAttributes(array('onBlur' => $onBlur . $valFunc,
2098                                              'onChange' => $onChange . $valFunc));
2099         }
2100 //  do not rely on frm function parameter, because htmlarea breaks it when overloading the onsubmit method
2101         $js .= '
2102 function validate_' . $this->_formName . '(frm) {
2103   if (skipClientValidation) {
2104      return true;
2105   }
2106   var ret = true;
2108   var frm = document.getElementById(\''. $this->_attributes['id'] .'\')
2109   var first_focus = false;
2110 ' . $validateJS . ';
2111   return ret;
2113 //]]>
2114 </script>';
2115         return $js;
2116     } // end func getValidationScript
2118     /**
2119      * Sets default error message
2120      */
2121     function _setDefaultRuleMessages(){
2122         foreach ($this->_rules as $field => $rulesarr){
2123             foreach ($rulesarr as $key => $rule){
2124                 if ($rule['message']===null){
2125                     $a=new stdClass();
2126                     $a->format=$rule['format'];
2127                     $str=get_string('err_'.$rule['type'], 'form', $a);
2128                     if (strpos($str, '[[')!==0){
2129                         $this->_rules[$field][$key]['message']=$str;
2130                     }
2131                 }
2132             }
2133         }
2134     }
2136     /**
2137      * Get list of attributes which have dependencies
2138      *
2139      * @return array
2140      */
2141     function getLockOptionObject(){
2142         $result = array();
2143         foreach ($this->_dependencies as $dependentOn => $conditions){
2144             $result[$dependentOn] = array();
2145             foreach ($conditions as $condition=>$values) {
2146                 $result[$dependentOn][$condition] = array();
2147                 foreach ($values as $value=>$dependents) {
2148                     $result[$dependentOn][$condition][$value] = array();
2149                     $i = 0;
2150                     foreach ($dependents as $dependent) {
2151                         $elements = $this->_getElNamesRecursive($dependent);
2152                         if (empty($elements)) {
2153                             // probably element inside of some group
2154                             $elements = array($dependent);
2155                         }
2156                         foreach($elements as $element) {
2157                             if ($element == $dependentOn) {
2158                                 continue;
2159                             }
2160                             $result[$dependentOn][$condition][$value][] = $element;
2161                         }
2162                     }
2163                 }
2164             }
2165         }
2166         return array($this->getAttribute('id'), $result);
2167     }
2169     /**
2170      * Get names of element or elements in a group.
2171      *
2172      * @param HTML_QuickForm_group|element $element element group or element object
2173      * @return array
2174      */
2175     function _getElNamesRecursive($element) {
2176         if (is_string($element)) {
2177             if (!$this->elementExists($element)) {
2178                 return array();
2179             }
2180             $element = $this->getElement($element);
2181         }
2183         if (is_a($element, 'HTML_QuickForm_group')) {
2184             $elsInGroup = $element->getElements();
2185             $elNames = array();
2186             foreach ($elsInGroup as $elInGroup){
2187                 if (is_a($elInGroup, 'HTML_QuickForm_group')) {
2188                     // not sure if this would work - groups nested in groups
2189                     $elNames = array_merge($elNames, $this->_getElNamesRecursive($elInGroup));
2190                 } else {
2191                     $elNames[] = $element->getElementName($elInGroup->getName());
2192                 }
2193             }
2195         } else if (is_a($element, 'HTML_QuickForm_header')) {
2196             return array();
2198         } else if (is_a($element, 'HTML_QuickForm_hidden')) {
2199             return array();
2201         } else if (method_exists($element, 'getPrivateName') &&
2202                 !($element instanceof HTML_QuickForm_advcheckbox)) {
2203             // The advcheckbox element implements a method called getPrivateName,
2204             // but in a way that is not compatible with the generic API, so we
2205             // have to explicitly exclude it.
2206             return array($element->getPrivateName());
2208         } else {
2209             $elNames = array($element->getName());
2210         }
2212         return $elNames;
2213     }
2215     /**
2216      * Adds a dependency for $elementName which will be disabled if $condition is met.
2217      * If $condition = 'notchecked' (default) then the condition is that the $dependentOn element
2218      * is not checked. If $condition = 'checked' then the condition is that the $dependentOn element
2219      * is checked. If $condition is something else (like "eq" for equals) then it is checked to see if the value
2220      * of the $dependentOn element is $condition (such as equal) to $value.
2221      *
2222      * @param string $elementName the name of the element which will be disabled
2223      * @param string $dependentOn the name of the element whose state will be checked for condition
2224      * @param string $condition the condition to check
2225      * @param mixed $value used in conjunction with condition.
2226      */
2227     function disabledIf($elementName, $dependentOn, $condition = 'notchecked', $value='1'){
2228         if (!array_key_exists($dependentOn, $this->_dependencies)) {
2229             $this->_dependencies[$dependentOn] = array();
2230         }
2231         if (!array_key_exists($condition, $this->_dependencies[$dependentOn])) {
2232             $this->_dependencies[$dependentOn][$condition] = array();
2233         }
2234         if (!array_key_exists($value, $this->_dependencies[$dependentOn][$condition])) {
2235             $this->_dependencies[$dependentOn][$condition][$value] = array();
2236         }
2237         $this->_dependencies[$dependentOn][$condition][$value][] = $elementName;
2238     }
2240     /**
2241      * Registers button as no submit button
2242      *
2243      * @param string $buttonname name of the button
2244      */
2245     function registerNoSubmitButton($buttonname){
2246         $this->_noSubmitButtons[]=$buttonname;
2247     }
2249     /**
2250      * Checks if button is a no submit button, i.e it doesn't submit form
2251      *
2252      * @param string $buttonname name of the button to check
2253      * @return bool
2254      */
2255     function isNoSubmitButton($buttonname){
2256         return (array_search($buttonname, $this->_noSubmitButtons)!==FALSE);
2257     }
2259     /**
2260      * Registers a button as cancel button
2261      *
2262      * @param string $addfieldsname name of the button
2263      */
2264     function _registerCancelButton($addfieldsname){
2265         $this->_cancelButtons[]=$addfieldsname;
2266     }
2268     /**
2269      * Displays elements without HTML input tags.
2270      * This method is different to freeze() in that it makes sure no hidden
2271      * elements are included in the form.
2272      * Note: If you want to make sure the submitted value is ignored, please use setDefaults().
2273      *
2274      * This function also removes all previously defined rules.
2275      *
2276      * @param string|array $elementList array or string of element(s) to be frozen
2277      * @return object|bool if element list is not empty then return error object, else true
2278      */
2279     function hardFreeze($elementList=null)
2280     {
2281         if (!isset($elementList)) {
2282             $this->_freezeAll = true;
2283             $elementList = array();
2284         } else {
2285             if (!is_array($elementList)) {
2286                 $elementList = preg_split('/[ ]*,[ ]*/', $elementList);
2287             }
2288             $elementList = array_flip($elementList);
2289         }
2291         foreach (array_keys($this->_elements) as $key) {
2292             $name = $this->_elements[$key]->getName();
2293             if ($this->_freezeAll || isset($elementList[$name])) {
2294                 $this->_elements[$key]->freeze();
2295                 $this->_elements[$key]->setPersistantFreeze(false);
2296                 unset($elementList[$name]);
2298                 // remove all rules
2299                 $this->_rules[$name] = array();
2300                 // if field is required, remove the rule
2301                 $unset = array_search($name, $this->_required);
2302                 if ($unset !== false) {
2303                     unset($this->_required[$unset]);
2304                 }
2305             }
2306         }
2308         if (!empty($elementList)) {
2309             return self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Nonexistant element(s): '" . implode("', '", array_keys($elementList)) . "' in HTML_QuickForm::freeze()", 'HTML_QuickForm_Error', true);
2310         }
2311         return true;
2312     }
2314     /**
2315      * Hard freeze all elements in a form except those whose names are in $elementList or hidden elements in a form.
2316      *
2317      * This function also removes all previously defined rules of elements it freezes.
2318      *
2319      * @throws HTML_QuickForm_Error
2320      * @param array $elementList array or string of element(s) not to be frozen
2321      * @return bool returns true
2322      */
2323     function hardFreezeAllVisibleExcept($elementList)
2324     {
2325         $elementList = array_flip($elementList);
2326         foreach (array_keys($this->_elements) as $key) {
2327             $name = $this->_elements[$key]->getName();
2328             $type = $this->_elements[$key]->getType();
2330             if ($type == 'hidden'){
2331                 // leave hidden types as they are
2332             } elseif (!isset($elementList[$name])) {
2333                 $this->_elements[$key]->freeze();
2334                 $this->_elements[$key]->setPersistantFreeze(false);
2336                 // remove all rules
2337                 $this->_rules[$name] = array();
2338                 // if field is required, remove the rule
2339                 $unset = array_search($name, $this->_required);
2340                 if ($unset !== false) {
2341                     unset($this->_required[$unset]);
2342                 }
2343             }
2344         }
2345         return true;
2346     }
2348    /**
2349     * Tells whether the form was already submitted
2350     *
2351     * This is useful since the _submitFiles and _submitValues arrays
2352     * may be completely empty after the trackSubmit value is removed.
2353     *
2354     * @return bool
2355     */
2356     function isSubmitted()
2357     {
2358         return parent::isSubmitted() && (!$this->isFrozen());
2359     }
2362 /**
2363  * MoodleQuickForm renderer
2364  *
2365  * A renderer for MoodleQuickForm that only uses XHTML and CSS and no
2366  * table tags, extends PEAR class HTML_QuickForm_Renderer_Tableless
2367  *
2368  * Stylesheet is part of standard theme and should be automatically included.
2369  *
2370  * @package   core_form
2371  * @copyright 2007 Jamie Pratt <me@jamiep.org>
2372  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2373  */
2374 class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
2376     /** @var array Element template array */
2377     var $_elementTemplates;
2379     /**
2380      * Template used when opening a hidden fieldset
2381      * (i.e. a fieldset that is opened when there is no header element)
2382      * @var string
2383      */
2384     var $_openHiddenFieldsetTemplate = "\n\t<fieldset class=\"hidden\"><div>";
2386     /** @var string Header Template string */
2387     var $_headerTemplate =
2388        "\n\t\t<legend class=\"ftoggler\">{header}</legend>\n\t\t<div class=\"fcontainer clearfix\">\n\t\t";
2390     /** @var string Template used when opening a fieldset */
2391     var $_openFieldsetTemplate = "\n\t<fieldset class=\"{classes}\" {id} {aria-live}>";
2393     /** @var string Template used when closing a fieldset */
2394     var $_closeFieldsetTemplate = "\n\t\t</div></fieldset>";
2396     /** @var string Required Note template string */
2397     var $_requiredNoteTemplate =
2398         "\n\t\t<div class=\"fdescription required\">{requiredNote}</div>";
2400     /** @var string Collapsible buttons string template */
2401     var $_collapseButtonsTemplate =
2402         "\n\t<div class=\"collapsible-actions\"><button disabled=\"disabled\" class=\"btn-expandall\">{strexpandall}</button>
2403         <button disabled=\"disabled\" class=\"btn-collapseall\">{strcollapseall}</button></div>";
2405     /**
2406      * Array whose keys are element names. If the key exists this is a advanced element
2407      *
2408      * @var array
2409      */
2410     var $_advancedElements = array();
2412     /**
2413      * Array whose keys are element names and the the boolean values reflect the current state. If the key exists this is a collapsible element.
2414      *
2415      * @var array
2416      */
2417     var $_collapsibleElements = array();
2419     /**
2420      * @var string Contains the collapsible buttons to add to the form.
2421      */
2422     var $_collapseButtons = '';
2424     /**
2425      * Constructor
2426      */
2427     function MoodleQuickForm_Renderer(){
2428         // switch next two lines for ol li containers for form items.
2429         //        $this->_elementTemplates=array('default'=>"\n\t\t".'<li class="fitem"><label>{label}{help}<!-- BEGIN required -->{req}<!-- END required --></label><div class="qfelement<!-- BEGIN error --> error<!-- END error --> {type}"><!-- BEGIN error --><span class="error">{error}</span><br /><!-- END error -->{element}</div></li>');
2430         $this->_elementTemplates = array(
2431         'default'=>"\n\t\t".'<div id="{id}" class="fitem {advanced}<!-- BEGIN required --> required<!-- END required --> fitem_{type}"><div class="fitemtitle"><label>{label}<!-- BEGIN required -->{req}<!-- END required -->{advancedimg}{help} </label></div><div class="felement {type}<!-- BEGIN error --> error<!-- END error -->"><!-- BEGIN error --><span class="error">{error}</span><br /><!-- END error -->{element}</div></div>',
2433         'actionbuttons'=>"\n\t\t".'<div id="{id}" class="fitem fitem_actionbuttons fitem_{type}"><div class="felement {type}">{element}</div></div>',
2435         'fieldset'=>"\n\t\t".'<div id="{id}" class="fitem {advanced}<!-- BEGIN required --> required<!-- END required --> fitem_{type}"><div class="fitemtitle"><div class="fgrouplabel"><label>{label}<!-- BEGIN required -->{req}<!-- END required -->{advancedimg}{help} </label></div></div><fieldset class="felement {type}<!-- BEGIN error --> error<!-- END error -->"><!-- BEGIN error --><span class="error">{error}</span><br /><!-- END error -->{element}</fieldset></div>',
2437         'static'=>"\n\t\t".'<div class="fitem {advanced}"><div class="fitemtitle"><div class="fstaticlabel"><label>{label}<!-- BEGIN required -->{req}<!-- END required -->{advancedimg}{help} </label></div></div><div class="felement fstatic <!-- BEGIN error --> error<!-- END error -->"><!-- BEGIN error --><span class="error">{error}</span><br /><!-- END error -->{element}&nbsp;</div></div>',
2439         'warning'=>"\n\t\t".'<div class="fitem {advanced}">{element}</div>',
2441         'nodisplay'=>'');
2443         parent::HTML_QuickForm_Renderer_Tableless();
2444     }
2446     /**
2447      * Set element's as adavance element
2448      *
2449      * @param array $elements form elements which needs to be grouped as advance elements.
2450      */
2451     function setAdvancedElements($elements){
2452         $this->_advancedElements = $elements;
2453     }
2455     /**
2456      * Setting collapsible elements
2457      *
2458      * @param array $elements
2459      */
2460     function setCollapsibleElements($elements) {
2461         $this->_collapsibleElements = $elements;
2462     }
2464     /**
2465      * What to do when starting the form
2466      *
2467      * @param MoodleQuickForm $form reference of the form
2468      */
2469     function startForm(&$form){
2470         global $PAGE;
2471         $this->_reqHTML = $form->getReqHTML();
2472         $this->_elementTemplates = str_replace('{req}', $this->_reqHTML, $this->_elementTemplates);
2473         $this->_advancedHTML = $form->getAdvancedHTML();
2474         $this->_collapseButtons = '';
2475         $formid = $form->getAttribute('id');
2476         parent::startForm($form);
2477         if ($form->isFrozen()){
2478             $this->_formTemplate = "\n<div class=\"mform frozen\">\n{content}\n</div>";
2479         } else {
2480             $this->_formTemplate = "\n<form{attributes}>\n\t<div style=\"display: none;\">{hidden}</div>\n{collapsebtns}\n{content}\n</form>";
2481             $this->_hiddenHtml .= $form->_pageparams;
2482         }
2484         if ($form->is_form_change_checker_enabled()) {
2485             $PAGE->requires->yui_module('moodle-core-formchangechecker',
2486                     'M.core_formchangechecker.init',
2487                     array(array(
2488                         'formid' => $formid
2489                     ))
2490             );
2491             $PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
2492         }
2493         if (!empty($this->_collapsibleElements)) {
2494             if (count($this->_collapsibleElements) > 1) {
2495                 $this->_collapseButtons = $this->_collapseButtonsTemplate;
2496                 $this->_collapseButtons = str_replace('{strcollapseall}', get_string('collapseall'), $this->_collapseButtons);
2497                 $this->_collapseButtons = str_replace('{strexpandall}', get_string('expandall'), $this->_collapseButtons);
2498             }
2499             $PAGE->requires->yui_module('moodle-form-shortforms', 'M.form.shortforms', array(array('formid' => $formid)));
2500         }
2501         if (!empty($this->_advancedElements)){
2502             $PAGE->requires->strings_for_js(array('showmore', 'showless'), 'form');
2503             $PAGE->requires->yui_module('moodle-form-showadvanced', 'M.form.showadvanced', array(array('formid' => $formid)));
2504         }
2505     }
2507     /**
2508      * Create advance group of elements
2509      *
2510      * @param object $group Passed by reference
2511      * @param bool $required if input is required field
2512      * @param string $error error message to display
2513      */
2514     function startGroup(&$group, $required, $error){
2515         // Make sure the element has an id.
2516         $group->_generateId();
2518         if (method_exists($group, 'getElementTemplateType')){
2519             $html = $this->_elementTemplates[$group->getElementTemplateType()];
2520         }else{
2521             $html = $this->_elementTemplates['default'];
2523         }
2525         if (isset($this->_advancedElements[$group->getName()])){
2526             $html =str_replace(' {advanced}', ' advanced', $html);
2527             $html =str_replace('{advancedimg}', $this->_advancedHTML, $html);
2528         } else {
2529             $html =str_replace(' {advanced}', '', $html);
2530             $html =str_replace('{advancedimg}', '', $html);
2531         }
2532         if (method_exists($group, 'getHelpButton')){
2533             $html =str_replace('{help}', $group->getHelpButton(), $html);
2534         }else{
2535             $html =str_replace('{help}', '', $html);
2536         }
2537         $html =str_replace('{id}', 'fgroup_' . $group->getAttribute('id'), $html);
2538         $html =str_replace('{name}', $group->getName(), $html);
2539         $html =str_replace('{type}', 'fgroup', $html);
2541         $this->_templates[$group->getName()]=$html;
2542         // Fix for bug in tableless quickforms that didn't allow you to stop a
2543         // fieldset before a group of elements.
2544         // if the element name indicates the end of a fieldset, close the fieldset
2545         if (   in_array($group->getName(), $this->_stopFieldsetElements)
2546             && $this->_fieldsetsOpen > 0
2547            ) {
2548             $this->_html .= $this->_closeFieldsetTemplate;
2549             $this->_fieldsetsOpen--;
2550         }
2551         parent::startGroup($group, $required, $error);
2552     }
2554     /**
2555      * Renders element
2556      *
2557      * @param HTML_QuickForm_element $element element
2558      * @param bool $required if input is required field
2559      * @param string $error error message to display
2560      */
2561     function renderElement(&$element, $required, $error){
2562         // Make sure the element has an id.
2563         $element->_generateId();
2565         //adding stuff to place holders in template
2566         //check if this is a group element first
2567         if (($this->_inGroup) and !empty($this->_groupElementTemplate)) {
2568             // so it gets substitutions for *each* element
2569             $html = $this->_groupElementTemplate;
2570         }
2571         elseif (method_exists($element, 'getElementTemplateType')){
2572             $html = $this->_elementTemplates[$element->getElementTemplateType()];
2573         }else{
2574             $html = $this->_elementTemplates['default'];
2575         }
2576         if (isset($this->_advancedElements[$element->getName()])){
2577             $html =str_replace(' {advanced}', ' advanced', $html);
2578         } else {
2579             $html =str_replace(' {advanced}', '', $html);
2580         }
2581         if (isset($this->_advancedElements[$element->getName()])||$element->getName() == 'mform_showadvanced'){
2582             $html =str_replace('{advancedimg}', $this->_advancedHTML, $html);
2583         } else {
2584             $html =str_replace('{advancedimg}', '', $html);
2585         }
2586         $html =str_replace('{id}', 'fitem_' . $element->getAttribute('id'), $html);
2587         $html =str_replace('{type}', 'f'.$element->getType(), $html);
2588         $html =str_replace('{name}', $element->getName(), $html);
2589         if (method_exists($element, 'getHelpButton')){
2590             $html = str_replace('{help}', $element->getHelpButton(), $html);
2591         }else{
2592             $html = str_replace('{help}', '', $html);
2594         }
2595         if (($this->_inGroup) and !empty($this->_groupElementTemplate)) {
2596             $this->_groupElementTemplate = $html;
2597         }
2598         elseif (!isset($this->_templates[$element->getName()])) {
2599             $this->_templates[$element->getName()] = $html;
2600         }
2602         parent::renderElement($element, $required, $error);
2603     }
2605     /**
2606      * Called when visiting a form, after processing all form elements
2607      * Adds required note, form attributes, validation javascript and form content.
2608      *
2609      * @global moodle_page $PAGE
2610      * @param moodleform $form Passed by reference
2611      */
2612     function finishForm(&$form){
2613         global $PAGE;
2614         if ($form->isFrozen()){
2615             $this->_hiddenHtml = '';
2616         }
2617         parent::finishForm($form);
2618         $this->_html = str_replace('{collapsebtns}', $this->_collapseButtons, $this->_html);
2619         if (!$form->isFrozen()) {
2620             $args = $form->getLockOptionObject();
2621             if (count($args[1]) > 0) {
2622                 $PAGE->requires->js_init_call('M.form.initFormDependencies', $args, true, moodleform::get_js_module());
2623             }
2624         }
2625     }
2626    /**
2627     * Called when visiting a header element
2628     *
2629     * @param HTML_QuickForm_header $header An HTML_QuickForm_header element being visited
2630     * @global moodle_page $PAGE
2631     */
2632     function renderHeader(&$header) {
2633         global $PAGE;
2635         $header->_generateId();
2636         $name = $header->getName();
2638         $id = empty($name) ? '' : ' id="' . $header->getAttribute('id') . '"';
2639         if (is_null($header->_text)) {
2640             $header_html = '';
2641         } elseif (!empty($name) && isset($this->_templates[$name])) {
2642             $header_html = str_replace('{header}', $header->toHtml(), $this->_templates[$name]);
2643         } else {
2644             $header_html = str_replace('{header}', $header->toHtml(), $this->_headerTemplate);
2645         }
2647         if ($this->_fieldsetsOpen > 0) {
2648             $this->_html .= $this->_closeFieldsetTemplate;
2649             $this->_fieldsetsOpen--;
2650         }
2652         // Define collapsible classes for fieldsets.
2653         $arialive = '';
2654         $fieldsetclasses = array('clearfix');
2655         if (isset($this->_collapsibleElements[$header->getName()])) {
2656             $fieldsetclasses[] = 'collapsible';
2657             $arialive = 'aria-live="polite"';
2658             if ($this->_collapsibleElements[$header->getName()]) {
2659                 $fieldsetclasses[] = 'collapsed';
2660             }
2661         }
2663         if (isset($this->_advancedElements[$name])){
2664             $fieldsetclasses[] = 'containsadvancedelements';
2665         }
2667         $openFieldsetTemplate = str_replace('{id}', $id, $this->_openFieldsetTemplate);
2668         $openFieldsetTemplate = str_replace('{classes}', join(' ', $fieldsetclasses), $openFieldsetTemplate);
2669         $openFieldsetTemplate = str_replace('{aria-live}', $arialive, $openFieldsetTemplate);
2671         $this->_html .= $openFieldsetTemplate . $header_html;
2672         $this->_fieldsetsOpen++;
2673     }
2675     /**
2676      * Return Array of element names that indicate the end of a fieldset
2677      *
2678      * @return array
2679      */
2680     function getStopFieldsetElements(){
2681         return $this->_stopFieldsetElements;
2682     }
2685 /**
2686  * Required elements validation
2687  *
2688  * This class overrides QuickForm validation since it allowed space or empty tag as a value
2689  *
2690  * @package   core_form
2691  * @category  form
2692  * @copyright 2006 Jamie Pratt <me@jamiep.org>
2693  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2694  */
2695 class MoodleQuickForm_Rule_Required extends HTML_QuickForm_Rule {
2696     /**
2697      * Checks if an element is not empty.
2698      * This is a server-side validation, it works for both text fields and editor fields
2699      *
2700      * @param string $value Value to check
2701      * @param int|string|array $options Not used yet
2702      * @return bool true if value is not empty
2703      */
2704     function validate($value, $options = null) {
2705         global $CFG;
2706         if (is_array($value) && array_key_exists('text', $value)) {
2707             $value = $value['text'];
2708         }
2709         if (is_array($value)) {
2710             // nasty guess - there has to be something in the array, hopefully nobody invents arrays in arrays
2711             $value = implode('', $value);
2712         }
2713         $stripvalues = array(
2714             '#</?(?!img|canvas|hr).*?>#im', // all tags except img, canvas and hr
2715             '#(\xc2|\xa0|\s|&nbsp;)#', //any whitespaces actually
2716         );
2717         if (!empty($CFG->strictformsrequired)) {
2718             $value = preg_replace($stripvalues, '', (string)$value);
2719         }
2720         if ((string)$value == '') {
2721             return false;
2722         }
2723         return true;
2724     }
2726     /**
2727      * This function returns Javascript code used to build client-side validation.
2728      * It checks if an element is not empty.
2729      *
2730      * @param int $format format of data which needs to be validated.
2731      * @return array
2732      */
2733     function getValidationScript($format = null) {
2734         global $CFG;
2735         if (!empty($CFG->strictformsrequired)) {
2736             if (!empty($format) && $format == FORMAT_HTML) {
2737                 return array('', "{jsVar}.replace(/(<[^img|hr|canvas]+>)|&nbsp;|\s+/ig, '') == ''");
2738             } else {
2739                 return array('', "{jsVar}.replace(/^\s+$/g, '') == ''");
2740             }
2741         } else {
2742             return array('', "{jsVar} == ''");
2743         }
2744     }
2747 /**
2748  * @global object $GLOBALS['_HTML_QuickForm_default_renderer']
2749  * @name $_HTML_QuickForm_default_renderer
2750  */
2751 $GLOBALS['_HTML_QuickForm_default_renderer'] = new MoodleQuickForm_Renderer();
2753 /** Please keep this list in alphabetical order. */
2754 MoodleQuickForm::registerElementType('advcheckbox', "$CFG->libdir/form/advcheckbox.php", 'MoodleQuickForm_advcheckbox');
2755 MoodleQuickForm::registerElementType('button', "$CFG->libdir/form/button.php", 'MoodleQuickForm_button');
2756 MoodleQuickForm::registerElementType('cancel', "$CFG->libdir/form/cancel.php", 'MoodleQuickForm_cancel');
2757 MoodleQuickForm::registerElementType('searchableselector', "$CFG->libdir/form/searchableselector.php", 'MoodleQuickForm_searchableselector');
2758 MoodleQuickForm::registerElementType('checkbox', "$CFG->libdir/form/checkbox.php", 'MoodleQuickForm_checkbox');
2759 MoodleQuickForm::registerElementType('date_selector', "$CFG->libdir/form/dateselector.php", 'MoodleQuickForm_date_selector');
2760 MoodleQuickForm::registerElementType('date_time_selector', "$CFG->libdir/form/datetimeselector.php", 'MoodleQuickForm_date_time_selector');
2761 MoodleQuickForm::registerElementType('duration', "$CFG->libdir/form/duration.php", 'MoodleQuickForm_duration');
2762 MoodleQuickForm::registerElementType('editor', "$CFG->libdir/form/editor.php", 'MoodleQuickForm_editor');
2763 MoodleQuickForm::registerElementType('filemanager', "$CFG->libdir/form/filemanager.php", 'MoodleQuickForm_filemanager');
2764 MoodleQuickForm::registerElementType('filepicker', "$CFG->libdir/form/filepicker.php", 'MoodleQuickForm_filepicker');
2765 MoodleQuickForm::registerElementType('grading', "$CFG->libdir/form/grading.php", 'MoodleQuickForm_grading');
2766 MoodleQuickForm::registerElementType('group', "$CFG->libdir/form/group.php", 'MoodleQuickForm_group');
2767 MoodleQuickForm::registerElementType('header', "$CFG->libdir/form/header.php", 'MoodleQuickForm_header');
2768 MoodleQuickForm::registerElementType('hidden', "$CFG->libdir/form/hidden.php", 'MoodleQuickForm_hidden');
2769 MoodleQuickForm::registerElementType('htmleditor', "$CFG->libdir/form/htmleditor.php", 'MoodleQuickForm_htmleditor');
2770 MoodleQuickForm::registerElementType('listing', "$CFG->libdir/form/listing.php", 'MoodleQuickForm_listing');
2771 MoodleQuickForm::registerElementType('modgrade', "$CFG->libdir/form/modgrade.php", 'MoodleQuickForm_modgrade');
2772 MoodleQuickForm::registerElementType('modvisible', "$CFG->libdir/form/modvisible.php", 'MoodleQuickForm_modvisible');
2773 MoodleQuickForm::registerElementType('password', "$CFG->libdir/form/password.php", 'MoodleQuickForm_password');
2774 MoodleQuickForm::registerElementType('passwordunmask', "$CFG->libdir/form/passwordunmask.php", 'MoodleQuickForm_passwordunmask');
2775 MoodleQuickForm::registerElementType('questioncategory', "$CFG->libdir/form/questioncategory.php", 'MoodleQuickForm_questioncategory');
2776 MoodleQuickForm::registerElementType('radio', "$CFG->libdir/form/radio.php", 'MoodleQuickForm_radio');
2777 MoodleQuickForm::registerElementType('recaptcha', "$CFG->libdir/form/recaptcha.php", 'MoodleQuickForm_recaptcha');
2778 MoodleQuickForm::registerElementType('select', "$CFG->libdir/form/select.php", 'MoodleQuickForm_select');
2779 MoodleQuickForm::registerElementType('selectgroups', "$CFG->libdir/form/selectgroups.php", 'MoodleQuickForm_selectgroups');
2780 MoodleQuickForm::registerElementType('selectwithlink', "$CFG->libdir/form/selectwithlink.php", 'MoodleQuickForm_selectwithlink');
2781 MoodleQuickForm::registerElementType('selectyesno', "$CFG->libdir/form/selectyesno.php", 'MoodleQuickForm_selectyesno');
2782 MoodleQuickForm::registerElementType('static', "$CFG->libdir/form/static.php", 'MoodleQuickForm_static');
2783 MoodleQuickForm::registerElementType('submit', "$CFG->libdir/form/submit.php", 'MoodleQuickForm_submit');
2784 MoodleQuickForm::registerElementType('submitlink', "$CFG->libdir/form/submitlink.php", 'MoodleQuickForm_submitlink');
2785 MoodleQuickForm::registerElementType('tags', "$CFG->libdir/form/tags.php", 'MoodleQuickForm_tags');
2786 MoodleQuickForm::registerElementType('text', "$CFG->libdir/form/text.php", 'MoodleQuickForm_text');
2787 MoodleQuickForm::registerElementType('textarea', "$CFG->libdir/form/textarea.php", 'MoodleQuickForm_textarea');
2788 MoodleQuickForm::registerElementType('url', "$CFG->libdir/form/url.php", 'MoodleQuickForm_url');
2789 MoodleQuickForm::registerElementType('warning', "$CFG->libdir/form/warning.php", 'MoodleQuickForm_warning');
2791 MoodleQuickForm::registerRule('required', null, 'MoodleQuickForm_Rule_Required', "$CFG->libdir/formslib.php");