Merge branch 'wip-MDL-20627-master' of git://github.com/abgreeve/moodle
[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  *
34  * @copyright  Jamie Pratt <me@jamiep.org>
35  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36  * @package    core
37  * @subpackage form
38  */
40 defined('MOODLE_INTERNAL') || die();
42 /** setup.php includes our hacked pear libs first */
43 require_once 'HTML/QuickForm.php';
44 require_once 'HTML/QuickForm/DHTMLRulesTableless.php';
45 require_once 'HTML/QuickForm/Renderer/Tableless.php';
46 require_once 'HTML/QuickForm/Rule.php';
48 require_once $CFG->libdir.'/filelib.php';
50 define('EDITOR_UNLIMITED_FILES', -1);
52 /**
53  * Callback called when PEAR throws an error
54  *
55  * @param PEAR_Error $error
56  */
57 function pear_handle_error($error){
58     echo '<strong>'.$error->GetMessage().'</strong> '.$error->getUserInfo();
59     echo '<br /> <strong>Backtrace </strong>:';
60     print_object($error->backtrace);
61 }
63 if (!empty($CFG->debug) and $CFG->debug >= DEBUG_ALL){
64     PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'pear_handle_error');
65 }
67 /**
68  *
69  * @staticvar bool $done
70  * @global moodle_page $PAGE
71  */
72 function form_init_date_js() {
73     global $PAGE;
74     static $done = false;
75     if (!$done) {
76         $module   = 'moodle-form-dateselector';
77         $function = 'M.form.dateselector.init_date_selectors';
78         $config = array(array('firstdayofweek'=>get_string('firstdayofweek', 'langconfig')));
79         $PAGE->requires->yui_module($module, $function, $config);
80         $done = true;
81     }
82 }
84 /**
85  * Moodle specific wrapper that separates quickforms syntax from moodle code. You won't directly
86  * use this class you should write a class definition which extends this class or a more specific
87  * subclass such a moodleform_mod for each form you want to display and/or process with formslib.
88  *
89  * You will write your own definition() method which performs the form set up.
90  *
91  * @package   moodlecore
92  * @copyright Jamie Pratt <me@jamiep.org>
93  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
94  */
95 abstract class moodleform {
96     /** @var string */
97     protected $_formname;       // form name
98     /**
99      * quickform object definition
100      *
101      * @var MoodleQuickForm MoodleQuickForm
102      */
103     protected $_form;
104     /**
105      * globals workaround
106      *
107      * @var array
108      */
109     protected $_customdata;
110     /**
111      * definition_after_data executed flag
112      * @var object definition_finalized
113      */
114     protected $_definition_finalized = false;
116     /**
117      * The constructor function calls the abstract function definition() and it will then
118      * process and clean and attempt to validate incoming data.
119      *
120      * It will call your custom validate method to validate data and will also check any rules
121      * you have specified in definition using addRule
122      *
123      * The name of the form (id attribute of the form) is automatically generated depending on
124      * the name you gave the class extending moodleform. You should call your class something
125      * like
126      *
127      * @param mixed $action the action attribute for the form. If empty defaults to auto detect the
128      *                  current url. If a moodle_url object then outputs params as hidden variables.
129      * @param array $customdata if your form defintion method needs access to data such as $course
130      *               $cm, etc. to construct the form definition then pass it in this array. You can
131      *               use globals for somethings.
132      * @param string $method if you set this to anything other than 'post' then _GET and _POST will
133      *               be merged and used as incoming data to the form.
134      * @param string $target target frame for form submission. You will rarely use this. Don't use
135      *                  it if you don't need to as the target attribute is deprecated in xhtml
136      *                  strict.
137      * @param mixed $attributes you can pass a string of html attributes here or an array.
138      * @param bool $editable
139      * @return object moodleform
140      */
141     function moodleform($action=null, $customdata=null, $method='post', $target='', $attributes=null, $editable=true) {
142         if (empty($action)){
143             $action = strip_querystring(qualified_me());
144         }
145         // Assign custom data first, so that get_form_identifier can use it.
146         $this->_customdata = $customdata;
147         $this->_formname = $this->get_form_identifier();
149         $this->_form = new MoodleQuickForm($this->_formname, $method, $action, $target, $attributes);
150         if (!$editable){
151             $this->_form->hardFreeze();
152         }
154         $this->definition();
156         $this->_form->addElement('hidden', 'sesskey', null); // automatic sesskey protection
157         $this->_form->setType('sesskey', PARAM_RAW);
158         $this->_form->setDefault('sesskey', sesskey());
159         $this->_form->addElement('hidden', '_qf__'.$this->_formname, null);   // form submission marker
160         $this->_form->setType('_qf__'.$this->_formname, PARAM_RAW);
161         $this->_form->setDefault('_qf__'.$this->_formname, 1);
162         $this->_form->_setDefaultRuleMessages();
164         // we have to know all input types before processing submission ;-)
165         $this->_process_submission($method);
166     }
168     /**
169      * It should returns unique identifier for the form.
170      * Currently it will return class name, but in case two same forms have to be
171      * rendered on same page then override function to get unique form identifier.
172      * e.g This is used on multiple self enrollments page.
173      *
174      * @return string form identifier.
175      */
176     protected function get_form_identifier() {
177         return get_class($this);
178     }
180     /**
181      * To autofocus on first form element or first element with error.
182      *
183      * @param string $name if this is set then the focus is forced to a field with this name
184      *
185      * @return string  javascript to select form element with first error or
186      *                  first element if no errors. Use this as a parameter
187      *                  when calling print_header
188      */
189     function focus($name=NULL) {
190         $form =& $this->_form;
191         $elkeys = array_keys($form->_elementIndex);
192         $error = false;
193         if (isset($form->_errors) &&  0 != count($form->_errors)){
194             $errorkeys = array_keys($form->_errors);
195             $elkeys = array_intersect($elkeys, $errorkeys);
196             $error = true;
197         }
199         if ($error or empty($name)) {
200             $names = array();
201             while (empty($names) and !empty($elkeys)) {
202                 $el = array_shift($elkeys);
203                 $names = $form->_getElNamesRecursive($el);
204             }
205             if (!empty($names)) {
206                 $name = array_shift($names);
207             }
208         }
210         $focus = '';
211         if (!empty($name)) {
212             $focus = 'forms[\''.$form->getAttribute('id').'\'].elements[\''.$name.'\']';
213         }
215         return $focus;
216      }
218     /**
219      * Internal method. Alters submitted data to be suitable for quickforms processing.
220      * Must be called when the form is fully set up.
221      *
222      * @param string $method
223      */
224     function _process_submission($method) {
225         $submission = array();
226         if ($method == 'post') {
227             if (!empty($_POST)) {
228                 $submission = $_POST;
229             }
230         } else {
231             $submission = array_merge_recursive($_GET, $_POST); // emulate handling of parameters in xxxx_param()
232         }
234         // following trick is needed to enable proper sesskey checks when using GET forms
235         // the _qf__.$this->_formname serves as a marker that form was actually submitted
236         if (array_key_exists('_qf__'.$this->_formname, $submission) and $submission['_qf__'.$this->_formname] == 1) {
237             if (!confirm_sesskey()) {
238                 print_error('invalidsesskey');
239             }
240             $files = $_FILES;
241         } else {
242             $submission = array();
243             $files = array();
244         }
246         $this->_form->updateSubmission($submission, $files);
247     }
249     /**
250      * Internal method. Validates all old-style deprecated uploaded files.
251      * The new way is to upload files via repository api.
252      *
253      * @global object
254      * @global object
255      * @param array $files
256      * @return bool|array Success or an array of errors
257      */
258     function _validate_files(&$files) {
259         global $CFG, $COURSE;
261         $files = array();
263         if (empty($_FILES)) {
264             // we do not need to do any checks because no files were submitted
265             // note: server side rules do not work for files - use custom verification in validate() instead
266             return true;
267         }
269         $errors = array();
270         $filenames = array();
272         // now check that we really want each file
273         foreach ($_FILES as $elname=>$file) {
274             $required = $this->_form->isElementRequired($elname);
276             if ($file['error'] == 4 and $file['size'] == 0) {
277                 if ($required) {
278                     $errors[$elname] = get_string('required');
279                 }
280                 unset($_FILES[$elname]);
281                 continue;
282             }
284             if (!empty($file['error'])) {
285                 $errors[$elname] = file_get_upload_error($file['error']);
286                 unset($_FILES[$elname]);
287                 continue;
288             }
290             if (!is_uploaded_file($file['tmp_name'])) {
291                 // TODO: improve error message
292                 $errors[$elname] = get_string('error');
293                 unset($_FILES[$elname]);
294                 continue;
295             }
297             if (!$this->_form->elementExists($elname) or !$this->_form->getElementType($elname)=='file') {
298                 // hmm, this file was not requested
299                 unset($_FILES[$elname]);
300                 continue;
301             }
303 /*
304   // TODO: rethink the file scanning MDL-19380
305             if ($CFG->runclamonupload) {
306                 if (!clam_scan_moodle_file($_FILES[$elname], $COURSE)) {
307                     $errors[$elname] = $_FILES[$elname]['uploadlog'];
308                     unset($_FILES[$elname]);
309                     continue;
310                 }
311             }
312 */
313             $filename = clean_param($_FILES[$elname]['name'], PARAM_FILE);
314             if ($filename === '') {
315                 // TODO: improve error message - wrong chars
316                 $errors[$elname] = get_string('error');
317                 unset($_FILES[$elname]);
318                 continue;
319             }
320             if (in_array($filename, $filenames)) {
321                 // TODO: improve error message - duplicate name
322                 $errors[$elname] = get_string('error');
323                 unset($_FILES[$elname]);
324                 continue;
325             }
326             $filenames[] = $filename;
327             $_FILES[$elname]['name'] = $filename;
329             $files[$elname] = $_FILES[$elname]['tmp_name'];
330         }
332         // return errors if found
333         if (count($errors) == 0){
334             return true;
336         } else {
337             $files = array();
338             return $errors;
339         }
340     }
342     /**
343      * Internal method. Validates filepicker and filemanager files if they are
344      * set as required fields. Also, sets the error message if encountered one.
345      *
346      * @return bool/array with errors
347      */
348     protected function validate_draft_files() {
349         global $USER;
350         $mform =& $this->_form;
352         $errors = array();
353         //Go through all the required elements and make sure you hit filepicker or
354         //filemanager element.
355         foreach ($mform->_rules as $elementname => $rules) {
356             $elementtype = $mform->getElementType($elementname);
357             //If element is of type filepicker then do validation
358             if (($elementtype == 'filepicker') || ($elementtype == 'filemanager')){
359                 //Check if rule defined is required rule
360                 foreach ($rules as $rule) {
361                     if ($rule['type'] == 'required') {
362                         $draftid = (int)$mform->getSubmitValue($elementname);
363                         $fs = get_file_storage();
364                         $context = get_context_instance(CONTEXT_USER, $USER->id);
365                         if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
366                             $errors[$elementname] = $rule['message'];
367                         }
368                     }
369                 }
370             }
371         }
372         if (empty($errors)) {
373             return true;
374         } else {
375             return $errors;
376         }
377     }
379     /**
380      * Load in existing data as form defaults. Usually new entry defaults are stored directly in
381      * form definition (new entry form); this function is used to load in data where values
382      * already exist and data is being edited (edit entry form).
383      *
384      * note: $slashed param removed
385      *
386      * @param mixed $default_values object or array of default values
387      */
388     function set_data($default_values) {
389         if (is_object($default_values)) {
390             $default_values = (array)$default_values;
391         }
392         $this->_form->setDefaults($default_values);
393     }
395     /**
396      * @deprecated
397      */
398     function set_upload_manager($um=false) {
399         debugging('Old file uploads can not be used any more, please use new filepicker element');
400     }
402     /**
403      * Check that form was submitted. Does not check validity of submitted data.
404      *
405      * @return bool true if form properly submitted
406      */
407     function is_submitted() {
408         return $this->_form->isSubmitted();
409     }
411     /**
412      * @staticvar bool $nosubmit
413      */
414     function no_submit_button_pressed(){
415         static $nosubmit = null; // one check is enough
416         if (!is_null($nosubmit)){
417             return $nosubmit;
418         }
419         $mform =& $this->_form;
420         $nosubmit = false;
421         if (!$this->is_submitted()){
422             return false;
423         }
424         foreach ($mform->_noSubmitButtons as $nosubmitbutton){
425             if (optional_param($nosubmitbutton, 0, PARAM_RAW)){
426                 $nosubmit = true;
427                 break;
428             }
429         }
430         return $nosubmit;
431     }
434     /**
435      * Check that form data is valid.
436      * You should almost always use this, rather than {@see validate_defined_fields}
437      *
438      * @staticvar bool $validated
439      * @return bool true if form data valid
440      */
441     function is_validated() {
442         //finalize the form definition before any processing
443         if (!$this->_definition_finalized) {
444             $this->_definition_finalized = true;
445             $this->definition_after_data();
446         }
448         return $this->validate_defined_fields();
449     }
451     /**
452      * Validate the form.
453      *
454      * You almost always want to call {@see is_validated} instead of this
455      * because it calls {@see definition_after_data} first, before validating the form,
456      * which is what you want in 99% of cases.
457      *
458      * This is provided as a separate function for those special cases where
459      * you want the form validated before definition_after_data is called
460      * for example, to selectively add new elements depending on a no_submit_button press,
461      * but only when the form is valid when the no_submit_button is pressed,
462      *
463      * @param boolean $validateonnosubmit optional, defaults to false.  The default behaviour
464      *                is NOT to validate the form when a no submit button has been pressed.
465      *                pass true here to override this behaviour
466      *
467      * @return bool true if form data valid
468      */
469     function validate_defined_fields($validateonnosubmit=false) {
470         static $validated = null; // one validation is enough
471         $mform =& $this->_form;
472         if ($this->no_submit_button_pressed() && empty($validateonnosubmit)){
473             return false;
474         } elseif ($validated === null) {
475             $internal_val = $mform->validate();
477             $files = array();
478             $file_val = $this->_validate_files($files);
479             //check draft files for validation and flag them if required files
480             //are not in draft area.
481             $draftfilevalue = $this->validate_draft_files();
483             if ($file_val !== true && $draftfilevalue !== true) {
484                 $file_val = array_merge($file_val, $draftfilevalue);
485             } else if ($draftfilevalue !== true) {
486                 $file_val = $draftfilevalue;
487             } //default is file_val, so no need to assign.
489             if ($file_val !== true) {
490                 if (!empty($file_val)) {
491                     foreach ($file_val as $element=>$msg) {
492                         $mform->setElementError($element, $msg);
493                     }
494                 }
495                 $file_val = false;
496             }
498             $data = $mform->exportValues();
499             $moodle_val = $this->validation($data, $files);
500             if ((is_array($moodle_val) && count($moodle_val)!==0)) {
501                 // non-empty array means errors
502                 foreach ($moodle_val as $element=>$msg) {
503                     $mform->setElementError($element, $msg);
504                 }
505                 $moodle_val = false;
507             } else {
508                 // anything else means validation ok
509                 $moodle_val = true;
510             }
512             $validated = ($internal_val and $moodle_val and $file_val);
513         }
514         return $validated;
515     }
517     /**
518      * Return true if a cancel button has been pressed resulting in the form being submitted.
519      *
520      * @return boolean true if a cancel button has been pressed
521      */
522     function is_cancelled(){
523         $mform =& $this->_form;
524         if ($mform->isSubmitted()){
525             foreach ($mform->_cancelButtons as $cancelbutton){
526                 if (optional_param($cancelbutton, 0, PARAM_RAW)){
527                     return true;
528                 }
529             }
530         }
531         return false;
532     }
534     /**
535      * Return submitted data if properly submitted or returns NULL if validation fails or
536      * if there is no submitted data.
537      *
538      * note: $slashed param removed
539      *
540      * @return object submitted data; NULL if not valid or not submitted or cancelled
541      */
542     function get_data() {
543         $mform =& $this->_form;
545         if (!$this->is_cancelled() and $this->is_submitted() and $this->is_validated()) {
546             $data = $mform->exportValues();
547             unset($data['sesskey']); // we do not need to return sesskey
548             unset($data['_qf__'.$this->_formname]);   // we do not need the submission marker too
549             if (empty($data)) {
550                 return NULL;
551             } else {
552                 return (object)$data;
553             }
554         } else {
555             return NULL;
556         }
557     }
559     /**
560      * Return submitted data without validation or NULL if there is no submitted data.
561      * note: $slashed param removed
562      *
563      * @return object submitted data; NULL if not submitted
564      */
565     function get_submitted_data() {
566         $mform =& $this->_form;
568         if ($this->is_submitted()) {
569             $data = $mform->exportValues();
570             unset($data['sesskey']); // we do not need to return sesskey
571             unset($data['_qf__'.$this->_formname]);   // we do not need the submission marker too
572             if (empty($data)) {
573                 return NULL;
574             } else {
575                 return (object)$data;
576             }
577         } else {
578             return NULL;
579         }
580     }
582     /**
583      * Save verified uploaded files into directory. Upload process can be customised from definition()
584      * NOTE: please use save_stored_file() or save_file()
585      *
586      * @return bool Always false
587      */
588     function save_files($destination) {
589         debugging('Not used anymore, please fix code! Use save_stored_file() or save_file() instead');
590         return false;
591     }
593     /**
594      * Returns name of uploaded file.
595      *
596      * @global object
597      * @param string $elname, first element if null
598      * @return mixed false in case of failure, string if ok
599      */
600     function get_new_filename($elname=null) {
601         global $USER;
603         if (!$this->is_submitted() or !$this->is_validated()) {
604             return false;
605         }
607         if (is_null($elname)) {
608             if (empty($_FILES)) {
609                 return false;
610             }
611             reset($_FILES);
612             $elname = key($_FILES);
613         }
615         if (empty($elname)) {
616             return false;
617         }
619         $element = $this->_form->getElement($elname);
621         if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) {
622             $values = $this->_form->exportValues($elname);
623             if (empty($values[$elname])) {
624                 return false;
625             }
626             $draftid = $values[$elname];
627             $fs = get_file_storage();
628             $context = get_context_instance(CONTEXT_USER, $USER->id);
629             if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
630                 return false;
631             }
632             $file = reset($files);
633             return $file->get_filename();
634         }
636         if (!isset($_FILES[$elname])) {
637             return false;
638         }
640         return $_FILES[$elname]['name'];
641     }
643     /**
644      * Save file to standard filesystem
645      *
646      * @global object
647      * @param string $elname name of element
648      * @param string $pathname full path name of file
649      * @param bool $override override file if exists
650      * @return bool success
651      */
652     function save_file($elname, $pathname, $override=false) {
653         global $USER;
655         if (!$this->is_submitted() or !$this->is_validated()) {
656             return false;
657         }
658         if (file_exists($pathname)) {
659             if ($override) {
660                 if (!@unlink($pathname)) {
661                     return false;
662                 }
663             } else {
664                 return false;
665             }
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 = get_context_instance(CONTEXT_USER, $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);
683             return $file->copy_content_to($pathname);
685         } else if (isset($_FILES[$elname])) {
686             return copy($_FILES[$elname]['tmp_name'], $pathname);
687         }
689         return false;
690     }
692     /**
693      * Returns a temporary file, do not forget to delete after not needed any more.
694      *
695      * @param string $elname
696      * @return string or false
697      */
698     function save_temp_file($elname) {
699         if (!$this->get_new_filename($elname)) {
700             return false;
701         }
702         if (!$dir = make_temp_directory('forms')) {
703             return false;
704         }
705         if (!$tempfile = tempnam($dir, 'tempup_')) {
706             return false;
707         }
708         if (!$this->save_file($elname, $tempfile, true)) {
709             // something went wrong
710             @unlink($tempfile);
711             return false;
712         }
714         return $tempfile;
715     }
717     /**
718      * Get draft files of a form element
719      * This is a protected method which will be used only inside moodleforms
720      *
721      * @global object $USER
722      * @param string $elname name of element
723      * @return array
724      */
725     protected function get_draft_files($elname) {
726         global $USER;
728         if (!$this->is_submitted()) {
729             return false;
730         }
732         $element = $this->_form->getElement($elname);
734         if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) {
735             $values = $this->_form->exportValues($elname);
736             if (empty($values[$elname])) {
737                 return false;
738             }
739             $draftid = $values[$elname];
740             $fs = get_file_storage();
741             $context = get_context_instance(CONTEXT_USER, $USER->id);
742             if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
743                 return null;
744             }
745             return $files;
746         }
747         return null;
748     }
750     /**
751      * Save file to local filesystem pool
752      *
753      * @global object
754      * @param string $elname name of element
755      * @param int $newcontextid
756      * @param string $newfilearea
757      * @param string $newfilepath
758      * @param string $newfilename - use specified filename, if not specified name of uploaded file used
759      * @param bool $overwrite  - overwrite file if exists
760      * @param int $newuserid - new userid if required
761      * @return mixed stored_file object or false if error; may throw exception if duplicate found
762      */
763     function save_stored_file($elname, $newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath='/',
764                               $newfilename=null, $overwrite=false, $newuserid=null) {
765         global $USER;
767         if (!$this->is_submitted() or !$this->is_validated()) {
768             return false;
769         }
771         if (empty($newuserid)) {
772             $newuserid = $USER->id;
773         }
775         $element = $this->_form->getElement($elname);
776         $fs = get_file_storage();
778         if ($element instanceof MoodleQuickForm_filepicker) {
779             $values = $this->_form->exportValues($elname);
780             if (empty($values[$elname])) {
781                 return false;
782             }
783             $draftid = $values[$elname];
784             $context = get_context_instance(CONTEXT_USER, $USER->id);
785             if (!$files = $fs->get_area_files($context->id, 'user' ,'draft', $draftid, 'id DESC', false)) {
786                 return false;
787             }
788             $file = reset($files);
789             if (is_null($newfilename)) {
790                 $newfilename = $file->get_filename();
791             }
793             if ($overwrite) {
794                 if ($oldfile = $fs->get_file($newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath, $newfilename)) {
795                     if (!$oldfile->delete()) {
796                         return false;
797                     }
798                 }
799             }
801             $file_record = array('contextid'=>$newcontextid, 'component'=>$newcomponent, 'filearea'=>$newfilearea, 'itemid'=>$newitemid,
802                                  'filepath'=>$newfilepath, 'filename'=>$newfilename, 'userid'=>$newuserid);
803             return $fs->create_file_from_storedfile($file_record, $file);
805         } else if (isset($_FILES[$elname])) {
806             $filename = is_null($newfilename) ? $_FILES[$elname]['name'] : $newfilename;
808             if ($overwrite) {
809                 if ($oldfile = $fs->get_file($newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath, $newfilename)) {
810                     if (!$oldfile->delete()) {
811                         return false;
812                     }
813                 }
814             }
816             $file_record = array('contextid'=>$newcontextid, 'component'=>$newcomponent, 'filearea'=>$newfilearea, 'itemid'=>$newitemid,
817                                  'filepath'=>$newfilepath, 'filename'=>$newfilename, 'userid'=>$newuserid);
818             return $fs->create_file_from_pathname($file_record, $_FILES[$elname]['tmp_name']);
819         }
821         return false;
822     }
824     /**
825      * Get content of uploaded file.
826      *
827      * @global object
828      * @param $element name of file upload element
829      * @return mixed false in case of failure, string if ok
830      */
831     function get_file_content($elname) {
832         global $USER;
834         if (!$this->is_submitted() or !$this->is_validated()) {
835             return false;
836         }
838         $element = $this->_form->getElement($elname);
840         if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) {
841             $values = $this->_form->exportValues($elname);
842             if (empty($values[$elname])) {
843                 return false;
844             }
845             $draftid = $values[$elname];
846             $fs = get_file_storage();
847             $context = get_context_instance(CONTEXT_USER, $USER->id);
848             if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
849                 return false;
850             }
851             $file = reset($files);
853             return $file->get_content();
855         } else if (isset($_FILES[$elname])) {
856             return file_get_contents($_FILES[$elname]['tmp_name']);
857         }
859         return false;
860     }
862     /**
863      * Print html form.
864      */
865     function display() {
866         //finalize the form definition if not yet done
867         if (!$this->_definition_finalized) {
868             $this->_definition_finalized = true;
869             $this->definition_after_data();
870         }
871         $this->_form->display();
872     }
874     /**
875      * Abstract method - always override!
876      */
877     protected abstract function definition();
879     /**
880      * Dummy stub method - override if you need to setup the form depending on current
881      * values. This method is called after definition(), data submission and set_data().
882      * All form setup that is dependent on form values should go in here.
883      */
884     function definition_after_data(){
885     }
887     /**
888      * Dummy stub method - override if you needed to perform some extra validation.
889      * If there are errors return array of errors ("fieldname"=>"error message"),
890      * otherwise true if ok.
891      *
892      * Server side rules do not work for uploaded files, implement serverside rules here if needed.
893      *
894      * @param array $data array of ("fieldname"=>value) of submitted data
895      * @param array $files array of uploaded files "element_name"=>tmp_file_path
896      * @return array of "element_name"=>"error_description" if there are errors,
897      *               or an empty array if everything is OK (true allowed for backwards compatibility too).
898      */
899     function validation($data, $files) {
900         return array();
901     }
903     /**
904      * Method to add a repeating group of elements to a form.
905      *
906      * @param array $elementobjs Array of elements or groups of elements that are to be repeated
907      * @param integer $repeats no of times to repeat elements initially
908      * @param array $options Array of options to apply to elements. Array keys are element names.
909      *                      This is an array of arrays. The second sets of keys are the option types
910      *                      for the elements :
911      *                          'default' - default value is value
912      *                          'type' - PARAM_* constant is value
913      *                          'helpbutton' - helpbutton params array is value
914      *                          'disabledif' - last three moodleform::disabledIf()
915      *                                           params are value as an array
916      * @param string $repeathiddenname name for hidden element storing no of repeats in this form
917      * @param string $addfieldsname name for button to add more fields
918      * @param int $addfieldsno how many fields to add at a time
919      * @param string $addstring name of button, {no} is replaced by no of blanks that will be added.
920      * @param boolean $addbuttoninside if true, don't call closeHeaderBefore($addfieldsname). Default false.
921      * @return int no of repeats of element in this page
922      */
923     function repeat_elements($elementobjs, $repeats, $options, $repeathiddenname,
924             $addfieldsname, $addfieldsno=5, $addstring=null, $addbuttoninside=false){
925         if ($addstring===null){
926             $addstring = get_string('addfields', 'form', $addfieldsno);
927         } else {
928             $addstring = str_ireplace('{no}', $addfieldsno, $addstring);
929         }
930         $repeats = optional_param($repeathiddenname, $repeats, PARAM_INT);
931         $addfields = optional_param($addfieldsname, '', PARAM_TEXT);
932         if (!empty($addfields)){
933             $repeats += $addfieldsno;
934         }
935         $mform =& $this->_form;
936         $mform->registerNoSubmitButton($addfieldsname);
937         $mform->addElement('hidden', $repeathiddenname, $repeats);
938         $mform->setType($repeathiddenname, PARAM_INT);
939         //value not to be overridden by submitted value
940         $mform->setConstants(array($repeathiddenname=>$repeats));
941         $namecloned = array();
942         for ($i = 0; $i < $repeats; $i++) {
943             foreach ($elementobjs as $elementobj){
944                 $elementclone = fullclone($elementobj);
945                 $name = $elementclone->getName();
946                 $namecloned[] = $name;
947                 if (!empty($name)) {
948                     $elementclone->setName($name."[$i]");
949                 }
950                 if (is_a($elementclone, 'HTML_QuickForm_header')) {
951                     $value = $elementclone->_text;
952                     $elementclone->setValue(str_replace('{no}', ($i+1), $value));
954                 } else {
955                     $value=$elementclone->getLabel();
956                     $elementclone->setLabel(str_replace('{no}', ($i+1), $value));
958                 }
960                 $mform->addElement($elementclone);
961             }
962         }
963         for ($i=0; $i<$repeats; $i++) {
964             foreach ($options as $elementname => $elementoptions){
965                 $pos=strpos($elementname, '[');
966                 if ($pos!==FALSE){
967                     $realelementname = substr($elementname, 0, $pos+1)."[$i]";
968                     $realelementname .= substr($elementname, $pos+1);
969                 }else {
970                     $realelementname = $elementname."[$i]";
971                 }
972                 foreach ($elementoptions as  $option => $params){
974                     switch ($option){
975                         case 'default' :
976                             $mform->setDefault($realelementname, $params);
977                             break;
978                         case 'helpbutton' :
979                             $params = array_merge(array($realelementname), $params);
980                             call_user_func_array(array(&$mform, 'addHelpButton'), $params);
981                             break;
982                         case 'disabledif' :
983                             foreach ($namecloned as $num => $name){
984                                 if ($params[0] == $name){
985                                     $params[0] = $params[0]."[$i]";
986                                     break;
987                                 }
988                             }
989                             $params = array_merge(array($realelementname), $params);
990                             call_user_func_array(array(&$mform, 'disabledIf'), $params);
991                             break;
992                         case 'rule' :
993                             if (is_string($params)){
994                                 $params = array(null, $params, null, 'client');
995                             }
996                             $params = array_merge(array($realelementname), $params);
997                             call_user_func_array(array(&$mform, 'addRule'), $params);
998                             break;
1000                     }
1001                 }
1002             }
1003         }
1004         $mform->addElement('submit', $addfieldsname, $addstring);
1006         if (!$addbuttoninside) {
1007             $mform->closeHeaderBefore($addfieldsname);
1008         }
1010         return $repeats;
1011     }
1013     /**
1014      * Adds a link/button that controls the checked state of a group of checkboxes.
1015      *
1016      * @global object
1017      * @param int    $groupid The id of the group of advcheckboxes this element controls
1018      * @param string $text The text of the link. Defaults to selectallornone ("select all/none")
1019      * @param array  $attributes associative array of HTML attributes
1020      * @param int    $originalValue The original general state of the checkboxes before the user first clicks this element
1021      */
1022     function add_checkbox_controller($groupid, $text = null, $attributes = null, $originalValue = 0) {
1023         global $CFG;
1025         // Set the default text if none was specified
1026         if (empty($text)) {
1027             $text = get_string('selectallornone', 'form');
1028         }
1030         $mform = $this->_form;
1031         $select_value = optional_param('checkbox_controller'. $groupid, null, PARAM_INT);
1033         if ($select_value == 0 || is_null($select_value)) {
1034             $new_select_value = 1;
1035         } else {
1036             $new_select_value = 0;
1037         }
1039         $mform->addElement('hidden', "checkbox_controller$groupid");
1040         $mform->setType("checkbox_controller$groupid", PARAM_INT);
1041         $mform->setConstants(array("checkbox_controller$groupid" => $new_select_value));
1043         $checkbox_controller_name = 'nosubmit_checkbox_controller' . $groupid;
1044         $mform->registerNoSubmitButton($checkbox_controller_name);
1046         // Prepare Javascript for submit element
1047         $js = "\n//<![CDATA[\n";
1048         if (!defined('HTML_QUICKFORM_CHECKBOXCONTROLLER_EXISTS')) {
1049             $js .= <<<EOS
1050 function html_quickform_toggle_checkboxes(group) {
1051     var checkboxes = document.getElementsByClassName('checkboxgroup' + group);
1052     var newvalue = false;
1053     var global = eval('html_quickform_checkboxgroup' + group + ';');
1054     if (global == 1) {
1055         eval('html_quickform_checkboxgroup' + group + ' = 0;');
1056         newvalue = '';
1057     } else {
1058         eval('html_quickform_checkboxgroup' + group + ' = 1;');
1059         newvalue = 'checked';
1060     }
1062     for (i = 0; i < checkboxes.length; i++) {
1063         checkboxes[i].checked = newvalue;
1064     }
1066 EOS;
1067             define('HTML_QUICKFORM_CHECKBOXCONTROLLER_EXISTS', true);
1068         }
1069         $js .= "\nvar html_quickform_checkboxgroup$groupid=$originalValue;\n";
1071         $js .= "//]]>\n";
1073         require_once("$CFG->libdir/form/submitlink.php");
1074         $submitlink = new MoodleQuickForm_submitlink($checkbox_controller_name, $attributes);
1075         $submitlink->_js = $js;
1076         $submitlink->_onclick = "html_quickform_toggle_checkboxes($groupid); return false;";
1077         $mform->addElement($submitlink);
1078         $mform->setDefault($checkbox_controller_name, $text);
1079     }
1081     /**
1082      * Use this method to a cancel and submit button to the end of your form. Pass a param of false
1083      * if you don't want a cancel button in your form. If you have a cancel button make sure you
1084      * check for it being pressed using is_cancelled() and redirecting if it is true before trying to
1085      * get data with get_data().
1086      *
1087      * @param boolean $cancel whether to show cancel button, default true
1088      * @param string $submitlabel label for submit button, defaults to get_string('savechanges')
1089      */
1090     function add_action_buttons($cancel = true, $submitlabel=null){
1091         if (is_null($submitlabel)){
1092             $submitlabel = get_string('savechanges');
1093         }
1094         $mform =& $this->_form;
1095         if ($cancel){
1096             //when two elements we need a group
1097             $buttonarray=array();
1098             $buttonarray[] = &$mform->createElement('submit', 'submitbutton', $submitlabel);
1099             $buttonarray[] = &$mform->createElement('cancel');
1100             $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
1101             $mform->closeHeaderBefore('buttonar');
1102         } else {
1103             //no group needed
1104             $mform->addElement('submit', 'submitbutton', $submitlabel);
1105             $mform->closeHeaderBefore('submitbutton');
1106         }
1107     }
1109     /**
1110      * Adds an initialisation call for a standard JavaScript enhancement.
1111      *
1112      * This function is designed to add an initialisation call for a JavaScript
1113      * enhancement that should exist within javascript-static M.form.init_{enhancementname}.
1114      *
1115      * Current options:
1116      *  - Selectboxes
1117      *      - smartselect:  Turns a nbsp indented select box into a custom drop down
1118      *                      control that supports multilevel and category selection.
1119      *                      $enhancement = 'smartselect';
1120      *                      $options = array('selectablecategories' => true|false)
1121      *
1122      * @since 2.0
1123      * @param string|element $element
1124      * @param string $enhancement
1125      * @param array $options
1126      * @param array $strings
1127      */
1128     function init_javascript_enhancement($element, $enhancement, array $options=array(), array $strings=null) {
1129         global $PAGE;
1130         if (is_string($element)) {
1131             $element = $this->_form->getElement($element);
1132         }
1133         if (is_object($element)) {
1134             $element->_generateId();
1135             $elementid = $element->getAttribute('id');
1136             $PAGE->requires->js_init_call('M.form.init_'.$enhancement, array($elementid, $options));
1137             if (is_array($strings)) {
1138                 foreach ($strings as $string) {
1139                     if (is_array($string)) {
1140                         call_user_method_array('string_for_js', $PAGE->requires, $string);
1141                     } else {
1142                         $PAGE->requires->string_for_js($string, 'moodle');
1143                     }
1144                 }
1145             }
1146         }
1147     }
1149     /**
1150      * Returns a JS module definition for the mforms JS
1151      * @return array
1152      */
1153     public static function get_js_module() {
1154         global $CFG;
1155         return array(
1156             'name' => 'mform',
1157             'fullpath' => '/lib/form/form.js',
1158             'requires' => array('base', 'node'),
1159             'strings' => array(
1160                 array('showadvanced', 'form'),
1161                 array('hideadvanced', 'form')
1162             )
1163         );
1164     }
1167 /**
1168  * You never extend this class directly. The class methods of this class are available from
1169  * the private $this->_form property on moodleform and its children. You generally only
1170  * call methods on this class from within abstract methods that you override on moodleform such
1171  * as definition and definition_after_data
1172  *
1173  * @package   moodlecore
1174  * @copyright Jamie Pratt <me@jamiep.org>
1175  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1176  */
1177 class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
1178     /** @var array */
1179     var $_types = array();
1180     var $_dependencies = array();
1181     /**
1182      * Array of buttons that if pressed do not result in the processing of the form.
1183      *
1184      * @var array
1185      */
1186     var $_noSubmitButtons=array();
1187     /**
1188      * Array of buttons that if pressed do not result in the processing of the form.
1189      *
1190      * @var array
1191      */
1192     var $_cancelButtons=array();
1194     /**
1195      * Array whose keys are element names. If the key exists this is a advanced element
1196      *
1197      * @var array
1198      */
1199     var $_advancedElements = array();
1201     /**
1202      * Whether to display advanced elements (on page load)
1203      *
1204      * @var boolean
1205      */
1206     var $_showAdvanced = null;
1208     /**
1209      * The form name is derived from the class name of the wrapper minus the trailing form
1210      * It is a name with words joined by underscores whereas the id attribute is words joined by
1211      * underscores.
1212      *
1213      * @var unknown_type
1214      */
1215     var $_formName = '';
1217     /**
1218      * String with the html for hidden params passed in as part of a moodle_url object for the action. Output in the form.
1219      *
1220      * @var string
1221      */
1222     var $_pageparams = '';
1224     /**
1225      * Class constructor - same parameters as HTML_QuickForm_DHTMLRulesTableless
1226      *
1227      * @global object
1228      * @staticvar int $formcounter
1229      * @param    string      $formName          Form's name.
1230      * @param    string      $method            (optional)Form's method defaults to 'POST'
1231      * @param    mixed      $action             (optional)Form's action - string or moodle_url
1232      * @param    string      $target            (optional)Form's target defaults to none
1233      * @param    mixed       $attributes        (optional)Extra attributes for <form> tag
1234      * @access   public
1235      */
1236     function MoodleQuickForm($formName, $method, $action, $target='', $attributes=null){
1237         global $CFG, $OUTPUT;
1239         static $formcounter = 1;
1241         HTML_Common::HTML_Common($attributes);
1242         $target = empty($target) ? array() : array('target' => $target);
1243         $this->_formName = $formName;
1244         if (is_a($action, 'moodle_url')){
1245             $this->_pageparams = html_writer::input_hidden_params($action);
1246             $action = $action->out_omit_querystring();
1247         } else {
1248             $this->_pageparams = '';
1249         }
1250         //no 'name' atttribute for form in xhtml strict :
1251         $attributes = array('action'=>$action, 'method'=>$method,
1252                 'accept-charset'=>'utf-8', 'id'=>'mform'.$formcounter) + $target;
1253         $formcounter++;
1254         $this->updateAttributes($attributes);
1256         //this is custom stuff for Moodle :
1257         $oldclass=   $this->getAttribute('class');
1258         if (!empty($oldclass)){
1259             $this->updateAttributes(array('class'=>$oldclass.' mform'));
1260         }else {
1261             $this->updateAttributes(array('class'=>'mform'));
1262         }
1263         $this->_reqHTML = '<img class="req" title="'.get_string('requiredelement', 'form').'" alt="'.get_string('requiredelement', 'form').'" src="'.$OUTPUT->pix_url('req') .'" />';
1264         $this->_advancedHTML = '<img class="adv" title="'.get_string('advancedelement', 'form').'" alt="'.get_string('advancedelement', 'form').'" src="'.$OUTPUT->pix_url('adv') .'" />';
1265         $this->setRequiredNote(get_string('somefieldsrequired', 'form', '<img alt="'.get_string('requiredelement', 'form').'" src="'.$OUTPUT->pix_url('req') .'" />'));
1266     }
1268     /**
1269      * Use this method to indicate an element in a form is an advanced field. If items in a form
1270      * are marked as advanced then 'Hide/Show Advanced' buttons will automatically be displayed in the
1271      * form so the user can decide whether to display advanced form controls.
1272      *
1273      * If you set a header element to advanced then all elements it contains will also be set as advanced.
1274      *
1275      * @param string $elementName group or element name (not the element name of something inside a group).
1276      * @param boolean $advanced default true sets the element to advanced. False removes advanced mark.
1277      */
1278     function setAdvanced($elementName, $advanced=true){
1279         if ($advanced){
1280             $this->_advancedElements[$elementName]='';
1281         } elseif (isset($this->_advancedElements[$elementName])) {
1282             unset($this->_advancedElements[$elementName]);
1283         }
1284         if ($advanced && $this->getElementType('mform_showadvanced_last')===false){
1285             $this->setShowAdvanced();
1286             $this->registerNoSubmitButton('mform_showadvanced');
1288             $this->addElement('hidden', 'mform_showadvanced_last');
1289             $this->setType('mform_showadvanced_last', PARAM_INT);
1290         }
1291     }
1292     /**
1293      * Set whether to show advanced elements in the form on first displaying form. Default is not to
1294      * display advanced elements in the form until 'Show Advanced' is pressed.
1295      *
1296      * You can get the last state of the form and possibly save it for this user by using
1297      * value 'mform_showadvanced_last' in submitted data.
1298      *
1299      * @param boolean $showadvancedNow
1300      */
1301     function setShowAdvanced($showadvancedNow = null){
1302         if ($showadvancedNow === null){
1303             if ($this->_showAdvanced !== null){
1304                 return;
1305             } else { //if setShowAdvanced is called without any preference
1306                      //make the default to not show advanced elements.
1307                 $showadvancedNow = get_user_preferences(
1308                                 moodle_strtolower($this->_formName.'_showadvanced', 0));
1309             }
1310         }
1311         //value of hidden element
1312         $hiddenLast = optional_param('mform_showadvanced_last', -1, PARAM_INT);
1313         //value of button
1314         $buttonPressed = optional_param('mform_showadvanced', 0, PARAM_RAW);
1315         //toggle if button pressed or else stay the same
1316         if ($hiddenLast == -1) {
1317             $next = $showadvancedNow;
1318         } elseif ($buttonPressed) { //toggle on button press
1319             $next = !$hiddenLast;
1320         } else {
1321             $next = $hiddenLast;
1322         }
1323         $this->_showAdvanced = $next;
1324         if ($showadvancedNow != $next){
1325             set_user_preference($this->_formName.'_showadvanced', $next);
1326         }
1327         $this->setConstants(array('mform_showadvanced_last'=>$next));
1328     }
1329     function getShowAdvanced(){
1330         return $this->_showAdvanced;
1331     }
1334    /**
1335     * Accepts a renderer
1336     *
1337     * @param object $renderer HTML_QuickForm_Renderer  An HTML_QuickForm_Renderer object
1338     * @access public
1339     * @return void
1340     */
1341     function accept(&$renderer) {
1342         if (method_exists($renderer, 'setAdvancedElements')){
1343             //check for visible fieldsets where all elements are advanced
1344             //and mark these headers as advanced as well.
1345             //And mark all elements in a advanced header as advanced
1346             $stopFields = $renderer->getStopFieldSetElements();
1347             $lastHeader = null;
1348             $lastHeaderAdvanced = false;
1349             $anyAdvanced = false;
1350             foreach (array_keys($this->_elements) as $elementIndex){
1351                 $element =& $this->_elements[$elementIndex];
1353                 // if closing header and any contained element was advanced then mark it as advanced
1354                 if ($element->getType()=='header' || in_array($element->getName(), $stopFields)){
1355                     if ($anyAdvanced && !is_null($lastHeader)){
1356                         $this->setAdvanced($lastHeader->getName());
1357                     }
1358                     $lastHeaderAdvanced = false;
1359                     unset($lastHeader);
1360                     $lastHeader = null;
1361                 } elseif ($lastHeaderAdvanced) {
1362                     $this->setAdvanced($element->getName());
1363                 }
1365                 if ($element->getType()=='header'){
1366                     $lastHeader =& $element;
1367                     $anyAdvanced = false;
1368                     $lastHeaderAdvanced = isset($this->_advancedElements[$element->getName()]);
1369                 } elseif (isset($this->_advancedElements[$element->getName()])){
1370                     $anyAdvanced = true;
1371                 }
1372             }
1373             // the last header may not be closed yet...
1374             if ($anyAdvanced && !is_null($lastHeader)){
1375                 $this->setAdvanced($lastHeader->getName());
1376             }
1377             $renderer->setAdvancedElements($this->_advancedElements);
1379         }
1380         parent::accept($renderer);
1381     }
1383     /**
1384      * @param string $elementName
1385      */
1386     function closeHeaderBefore($elementName){
1387         $renderer =& $this->defaultRenderer();
1388         $renderer->addStopFieldsetElements($elementName);
1389     }
1391     /**
1392      * Should be used for all elements of a form except for select, radio and checkboxes which
1393      * clean their own data.
1394      *
1395      * @param string $elementname
1396      * @param integer $paramtype use the constants PARAM_*.
1397      *     *  PARAM_CLEAN is deprecated and you should try to use a more specific type.
1398      *     *  PARAM_TEXT should be used for cleaning data that is expected to be plain text.
1399      *          It will strip all html tags. But will still let tags for multilang support
1400      *          through.
1401      *     *  PARAM_RAW means no cleaning whatsoever, it is used mostly for data from the
1402      *          html editor. Data from the editor is later cleaned before display using
1403      *          format_text() function. PARAM_RAW can also be used for data that is validated
1404      *          by some other way or printed by p() or s().
1405      *     *  PARAM_INT should be used for integers.
1406      *     *  PARAM_ACTION is an alias of PARAM_ALPHA and is used for hidden fields specifying
1407      *          form actions.
1408      */
1409     function setType($elementname, $paramtype) {
1410         $this->_types[$elementname] = $paramtype;
1411     }
1413     /**
1414      * See description of setType above. This can be used to set several types at once.
1415      *
1416      * @param array $paramtypes
1417      */
1418     function setTypes($paramtypes) {
1419         $this->_types = $paramtypes + $this->_types;
1420     }
1422     /**
1423      * @param array $submission
1424      * @param array $files
1425      */
1426     function updateSubmission($submission, $files) {
1427         $this->_flagSubmitted = false;
1429         if (empty($submission)) {
1430             $this->_submitValues = array();
1431         } else {
1432             foreach ($submission as $key=>$s) {
1433                 if (array_key_exists($key, $this->_types)) {
1434                     $type = $this->_types[$key];
1435                 } else {
1436                     $type = PARAM_RAW;
1437                 }
1438                 if (is_array($s)) {
1439                     $submission[$key] = clean_param_array($s, $type, true);
1440                 } else {
1441                     $submission[$key] = clean_param($s, $type);
1442                 }
1443             }
1444             $this->_submitValues = $submission;
1445             $this->_flagSubmitted = true;
1446         }
1448         if (empty($files)) {
1449             $this->_submitFiles = array();
1450         } else {
1451             $this->_submitFiles = $files;
1452             $this->_flagSubmitted = true;
1453         }
1455         // need to tell all elements that they need to update their value attribute.
1456          foreach (array_keys($this->_elements) as $key) {
1457              $this->_elements[$key]->onQuickFormEvent('updateValue', null, $this);
1458          }
1459     }
1461     /**
1462      * @return string
1463      */
1464     function getReqHTML(){
1465         return $this->_reqHTML;
1466     }
1468     /**
1469      * @return string
1470      */
1471     function getAdvancedHTML(){
1472         return $this->_advancedHTML;
1473     }
1475     /**
1476      * Initializes a default form value. Used to specify the default for a new entry where
1477      * no data is loaded in using moodleform::set_data()
1478      *
1479      * note: $slashed param removed
1480      *
1481      * @param     string   $elementname        element name
1482      * @param     mixed    $values             values for that element name
1483      * @access    public
1484      * @return    void
1485      */
1486     function setDefault($elementName, $defaultValue){
1487         $this->setDefaults(array($elementName=>$defaultValue));
1488     } // end func setDefault
1489     /**
1490      * Add an array of buttons to the form
1491      * @param    array       $buttons          An associative array representing help button to attach to
1492      *                                          to the form. keys of array correspond to names of elements in form.
1493      * @deprecated since Moodle 2.0 - use addHelpButton() call on each element manually
1494      * @param bool $suppresscheck
1495      * @param string $function
1496      * @access   public
1497     */
1498     function setHelpButtons($buttons, $suppresscheck=false, $function='helpbutton'){
1500         debugging('function moodle_form::setHelpButtons() is deprecated');
1501         //foreach ($buttons as $elementname => $button){
1502         //    $this->setHelpButton($elementname, $button, $suppresscheck, $function);
1503         //}
1504     }
1505     /**
1506      * Add a single button.
1507      *
1508      * @deprecated use addHelpButton() instead
1509      * @param string $elementname name of the element to add the item to
1510      * @param array $button arguments to pass to function $function
1511      * @param boolean $suppresscheck whether to throw an error if the element
1512      *                                  doesn't exist.
1513      * @param string $function - function to generate html from the arguments in $button
1514      * @param string $function
1515      */
1516     function setHelpButton($elementname, $buttonargs, $suppresscheck=false, $function='helpbutton'){
1517         global $OUTPUT;
1519         debugging('function moodle_form::setHelpButton() is deprecated');
1520         if ($function !== 'helpbutton') {
1521             //debugging('parameter $function in moodle_form::setHelpButton() is not supported any more');
1522         }
1524         $buttonargs = (array)$buttonargs;
1526         if (array_key_exists($elementname, $this->_elementIndex)) {
1527             //_elements has a numeric index, this code accesses the elements by name
1528             $element = $this->_elements[$this->_elementIndex[$elementname]];
1530             $page     = isset($buttonargs[0]) ? $buttonargs[0] : null;
1531             $text     = isset($buttonargs[1]) ? $buttonargs[1] : null;
1532             $module   = isset($buttonargs[2]) ? $buttonargs[2] : 'moodle';
1533             $linktext = isset($buttonargs[3]) ? $buttonargs[3] : false;
1535             $element->_helpbutton = $OUTPUT->old_help_icon($page, $text, $module, $linktext);
1537         } else if (!$suppresscheck) {
1538             print_error('nonexistentformelements', 'form', '', $elementname);
1539         }
1540     }
1542     /**
1543      * Add a help button to element, only one button per element is allowed.
1544      *
1545      * This is new, simplified and preferable method of setting a help icon on form elements.
1546      * It uses the new $OUTPUT->help_icon().
1547      *
1548      * Typically, you will provide the same identifier and the component as you have used for the
1549      * label of the element. The string identifier with the _help suffix added is then used
1550      * as the help string.
1551      *
1552      * There has to be two strings defined:
1553      *   1/ get_string($identifier, $component) - the title of the help page
1554      *   2/ get_string($identifier.'_help', $component) - the actual help page text
1555      *
1556      * @since 2.0
1557      * @param string $elementname name of the element to add the item to
1558      * @param string $identifier help string identifier without _help suffix
1559      * @param string $component component name to look the help string in
1560      * @param string $linktext optional text to display next to the icon
1561      * @param boolean $suppresscheck set to true if the element may not exist
1562      * @return void
1563      */
1564     function addHelpButton($elementname, $identifier, $component = 'moodle', $linktext = '', $suppresscheck = false) {
1565         global $OUTPUT;
1566         if (array_key_exists($elementname, $this->_elementIndex)) {
1567             $element = $this->_elements[$this->_elementIndex[$elementname]];
1568             $element->_helpbutton = $OUTPUT->help_icon($identifier, $component, $linktext);
1569         } else if (!$suppresscheck) {
1570             debugging(get_string('nonexistentformelements', 'form', $elementname));
1571         }
1572     }
1574     /**
1575      * Set constant value not overridden by _POST or _GET
1576      * note: this does not work for complex names with [] :-(
1577      *
1578      * @param string $elname name of element
1579      * @param mixed $value
1580      * @return void
1581      */
1582     function setConstant($elname, $value) {
1583         $this->_constantValues = HTML_QuickForm::arrayMerge($this->_constantValues, array($elname=>$value));
1584         $element =& $this->getElement($elname);
1585         $element->onQuickFormEvent('updateValue', null, $this);
1586     }
1588     /**
1589      * @param string $elementList
1590      */
1591     function exportValues($elementList = null){
1592         $unfiltered = array();
1593         if (null === $elementList) {
1594             // iterate over all elements, calling their exportValue() methods
1595             $emptyarray = array();
1596             foreach (array_keys($this->_elements) as $key) {
1597                 if ($this->_elements[$key]->isFrozen() && !$this->_elements[$key]->_persistantFreeze){
1598                     $value = $this->_elements[$key]->exportValue($emptyarray, true);
1599                 } else {
1600                     $value = $this->_elements[$key]->exportValue($this->_submitValues, true);
1601                 }
1603                 if (is_array($value)) {
1604                     // This shit throws a bogus warning in PHP 4.3.x
1605                     $unfiltered = HTML_QuickForm::arrayMerge($unfiltered, $value);
1606                 }
1607             }
1608         } else {
1609             if (!is_array($elementList)) {
1610                 $elementList = array_map('trim', explode(',', $elementList));
1611             }
1612             foreach ($elementList as $elementName) {
1613                 $value = $this->exportValue($elementName);
1614                 if (PEAR::isError($value)) {
1615                     return $value;
1616                 }
1617                 //oh, stock QuickFOrm was returning array of arrays!
1618                 $unfiltered = HTML_QuickForm::arrayMerge($unfiltered, $value);
1619             }
1620         }
1622         if (is_array($this->_constantValues)) {
1623             $unfiltered = HTML_QuickForm::arrayMerge($unfiltered, $this->_constantValues);
1624         }
1626         return $unfiltered;
1627     }
1628     /**
1629      * Adds a validation rule for the given field
1630      *
1631      * If the element is in fact a group, it will be considered as a whole.
1632      * To validate grouped elements as separated entities,
1633      * use addGroupRule instead of addRule.
1634      *
1635      * @param    string     $element       Form element name
1636      * @param    string     $message       Message to display for invalid data
1637      * @param    string     $type          Rule type, use getRegisteredRules() to get types
1638      * @param    string     $format        (optional)Required for extra rule data
1639      * @param    string     $validation    (optional)Where to perform validation: "server", "client"
1640      * @param    boolean    $reset         Client-side validation: reset the form element to its original value if there is an error?
1641      * @param    boolean    $force         Force the rule to be applied, even if the target form element does not exist
1642      * @access   public
1643      */
1644     function addRule($element, $message, $type, $format=null, $validation='server', $reset = false, $force = false)
1645     {
1646         parent::addRule($element, $message, $type, $format, $validation, $reset, $force);
1647         if ($validation == 'client') {
1648             $this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_formName . '; } catch(e) { return true; } return myValidator(this);'));
1649         }
1651     } // end func addRule
1652     /**
1653      * Adds a validation rule for the given group of elements
1654      *
1655      * Only groups with a name can be assigned a validation rule
1656      * Use addGroupRule when you need to validate elements inside the group.
1657      * Use addRule if you need to validate the group as a whole. In this case,
1658      * the same rule will be applied to all elements in the group.
1659      * Use addRule if you need to validate the group against a function.
1660      *
1661      * @param    string     $group         Form group name
1662      * @param    mixed      $arg1          Array for multiple elements or error message string for one element
1663      * @param    string     $type          (optional)Rule type use getRegisteredRules() to get types
1664      * @param    string     $format        (optional)Required for extra rule data
1665      * @param    int        $howmany       (optional)How many valid elements should be in the group
1666      * @param    string     $validation    (optional)Where to perform validation: "server", "client"
1667      * @param    bool       $reset         Client-side: whether to reset the element's value to its original state if validation failed.
1668      * @access   public
1669      */
1670     function addGroupRule($group, $arg1, $type='', $format=null, $howmany=0, $validation = 'server', $reset = false)
1671     {
1672         parent::addGroupRule($group, $arg1, $type, $format, $howmany, $validation, $reset);
1673         if (is_array($arg1)) {
1674              foreach ($arg1 as $rules) {
1675                 foreach ($rules as $rule) {
1676                     $validation = (isset($rule[3]) && 'client' == $rule[3])? 'client': 'server';
1678                     if ('client' == $validation) {
1679                         $this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_formName . '; } catch(e) { return true; } return myValidator(this);'));
1680                     }
1681                 }
1682             }
1683         } elseif (is_string($arg1)) {
1685             if ($validation == 'client') {
1686                 $this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_formName . '; } catch(e) { return true; } return myValidator(this);'));
1687             }
1688         }
1689     } // end func addGroupRule
1691     // }}}
1692     /**
1693      * Returns the client side validation script
1694      *
1695      * The code here was copied from HTML_QuickForm_DHTMLRulesTableless who copied it from  HTML_QuickForm
1696      * and slightly modified to run rules per-element
1697      * Needed to override this because of an error with client side validation of grouped elements.
1698      *
1699      * @access    public
1700      * @return    string    Javascript to perform validation, empty string if no 'client' rules were added
1701      */
1702     function getValidationScript()
1703     {
1704         if (empty($this->_rules) || empty($this->_attributes['onsubmit'])) {
1705             return '';
1706         }
1708         include_once('HTML/QuickForm/RuleRegistry.php');
1709         $registry =& HTML_QuickForm_RuleRegistry::singleton();
1710         $test = array();
1711         $js_escape = array(
1712             "\r"    => '\r',
1713             "\n"    => '\n',
1714             "\t"    => '\t',
1715             "'"     => "\\'",
1716             '"'     => '\"',
1717             '\\'    => '\\\\'
1718         );
1720         foreach ($this->_rules as $elementName => $rules) {
1721             foreach ($rules as $rule) {
1722                 if ('client' == $rule['validation']) {
1723                     unset($element); //TODO: find out how to properly initialize it
1725                     $dependent  = isset($rule['dependent']) && is_array($rule['dependent']);
1726                     $rule['message'] = strtr($rule['message'], $js_escape);
1728                     if (isset($rule['group'])) {
1729                         $group    =& $this->getElement($rule['group']);
1730                         // No JavaScript validation for frozen elements
1731                         if ($group->isFrozen()) {
1732                             continue 2;
1733                         }
1734                         $elements =& $group->getElements();
1735                         foreach (array_keys($elements) as $key) {
1736                             if ($elementName == $group->getElementName($key)) {
1737                                 $element =& $elements[$key];
1738                                 break;
1739                             }
1740                         }
1741                     } elseif ($dependent) {
1742                         $element   =  array();
1743                         $element[] =& $this->getElement($elementName);
1744                         foreach ($rule['dependent'] as $elName) {
1745                             $element[] =& $this->getElement($elName);
1746                         }
1747                     } else {
1748                         $element =& $this->getElement($elementName);
1749                     }
1750                     // No JavaScript validation for frozen elements
1751                     if (is_object($element) && $element->isFrozen()) {
1752                         continue 2;
1753                     } elseif (is_array($element)) {
1754                         foreach (array_keys($element) as $key) {
1755                             if ($element[$key]->isFrozen()) {
1756                                 continue 3;
1757                             }
1758                         }
1759                     }
1760                     //for editor element, [text] is appended to the name.
1761                     if ($element->getType() == 'editor') {
1762                         $elementName .= '[text]';
1763                         //Add format to rule as moodleform check which format is supported by browser
1764                         //it is not set anywhere... So small hack to make sure we pass it down to quickform
1765                         if (is_null($rule['format'])) {
1766                             $rule['format'] = $element->getFormat();
1767                         }
1768                     }
1769                     // Fix for bug displaying errors for elements in a group
1770                     $test[$elementName][0][] = $registry->getValidationScript($element, $elementName, $rule);
1771                     $test[$elementName][1]=$element;
1772                     //end of fix
1773                 }
1774             }
1775         }
1777         // Fix for MDL-9524. If you don't do this, then $element may be left as a reference to one of the fields in
1778         // the form, and then that form field gets corrupted by the code that follows.
1779         unset($element);
1781         $js = '
1782 <script type="text/javascript">
1783 //<![CDATA[
1785 var skipClientValidation = false;
1787 function qf_errorHandler(element, _qfMsg) {
1788   div = element.parentNode;
1790   if ((div == undefined) || (element.name == undefined)) {
1791     //no checking can be done for undefined elements so let server handle it.
1792     return true;
1793   }
1795   if (_qfMsg != \'\') {
1796     var errorSpan = document.getElementById(\'id_error_\'+element.name);
1797     if (!errorSpan) {
1798       errorSpan = document.createElement("span");
1799       errorSpan.id = \'id_error_\'+element.name;
1800       errorSpan.className = "error";
1801       element.parentNode.insertBefore(errorSpan, element.parentNode.firstChild);
1802     }
1804     while (errorSpan.firstChild) {
1805       errorSpan.removeChild(errorSpan.firstChild);
1806     }
1808     errorSpan.appendChild(document.createTextNode(_qfMsg.substring(3)));
1809     errorSpan.appendChild(document.createElement("br"));
1811     if (div.className.substr(div.className.length - 6, 6) != " error"
1812         && div.className != "error") {
1813       div.className += " error";
1814     }
1816     return false;
1817   } else {
1818     var errorSpan = document.getElementById(\'id_error_\'+element.name);
1819     if (errorSpan) {
1820       errorSpan.parentNode.removeChild(errorSpan);
1821     }
1823     if (div.className.substr(div.className.length - 6, 6) == " error") {
1824       div.className = div.className.substr(0, div.className.length - 6);
1825     } else if (div.className == "error") {
1826       div.className = "";
1827     }
1829     return true;
1830   }
1831 }';
1832         $validateJS = '';
1833         foreach ($test as $elementName => $jsandelement) {
1834             // Fix for bug displaying errors for elements in a group
1835             //unset($element);
1836             list($jsArr,$element)=$jsandelement;
1837             //end of fix
1838             $escapedElementName = preg_replace_callback(
1839                 '/[_\[\]]/',
1840                 create_function('$matches', 'return sprintf("_%2x",ord($matches[0]));'),
1841                 $elementName);
1842             $js .= '
1843 function validate_' . $this->_formName . '_' . $escapedElementName . '(element) {
1844   if (undefined == element) {
1845      //required element was not found, then let form be submitted without client side validation
1846      return true;
1847   }
1848   var value = \'\';
1849   var errFlag = new Array();
1850   var _qfGroups = {};
1851   var _qfMsg = \'\';
1852   var frm = element.parentNode;
1853   if ((undefined != element.name) && (frm != undefined)) {
1854       while (frm && frm.nodeName.toUpperCase() != "FORM") {
1855         frm = frm.parentNode;
1856       }
1857     ' . join("\n", $jsArr) . '
1858       return qf_errorHandler(element, _qfMsg);
1859   } else {
1860     //element name should be defined else error msg will not be displayed.
1861     return true;
1862   }
1864 ';
1865             $validateJS .= '
1866   ret = validate_' . $this->_formName . '_' . $escapedElementName.'(frm.elements[\''.$elementName.'\']) && ret;
1867   if (!ret && !first_focus) {
1868     first_focus = true;
1869     frm.elements[\''.$elementName.'\'].focus();
1870   }
1871 ';
1873             // Fix for bug displaying errors for elements in a group
1874             //unset($element);
1875             //$element =& $this->getElement($elementName);
1876             //end of fix
1877             $valFunc = 'validate_' . $this->_formName . '_' . $escapedElementName . '(this)';
1878             $onBlur = $element->getAttribute('onBlur');
1879             $onChange = $element->getAttribute('onChange');
1880             $element->updateAttributes(array('onBlur' => $onBlur . $valFunc,
1881                                              'onChange' => $onChange . $valFunc));
1882         }
1883 //  do not rely on frm function parameter, because htmlarea breaks it when overloading the onsubmit method
1884         $js .= '
1885 function validate_' . $this->_formName . '(frm) {
1886   if (skipClientValidation) {
1887      return true;
1888   }
1889   var ret = true;
1891   var frm = document.getElementById(\''. $this->_attributes['id'] .'\')
1892   var first_focus = false;
1893 ' . $validateJS . ';
1894   return ret;
1896 //]]>
1897 </script>';
1898         return $js;
1899     } // end func getValidationScript
1900     function _setDefaultRuleMessages(){
1901         foreach ($this->_rules as $field => $rulesarr){
1902             foreach ($rulesarr as $key => $rule){
1903                 if ($rule['message']===null){
1904                     $a=new stdClass();
1905                     $a->format=$rule['format'];
1906                     $str=get_string('err_'.$rule['type'], 'form', $a);
1907                     if (strpos($str, '[[')!==0){
1908                         $this->_rules[$field][$key]['message']=$str;
1909                     }
1910                 }
1911             }
1912         }
1913     }
1915     function getLockOptionObject(){
1916         $result = array();
1917         foreach ($this->_dependencies as $dependentOn => $conditions){
1918             $result[$dependentOn] = array();
1919             foreach ($conditions as $condition=>$values) {
1920                 $result[$dependentOn][$condition] = array();
1921                 foreach ($values as $value=>$dependents) {
1922                     $result[$dependentOn][$condition][$value] = array();
1923                     $i = 0;
1924                     foreach ($dependents as $dependent) {
1925                         $elements = $this->_getElNamesRecursive($dependent);
1926                         if (empty($elements)) {
1927                             // probably element inside of some group
1928                             $elements = array($dependent);
1929                         }
1930                         foreach($elements as $element) {
1931                             if ($element == $dependentOn) {
1932                                 continue;
1933                             }
1934                             $result[$dependentOn][$condition][$value][] = $element;
1935                         }
1936                     }
1937                 }
1938             }
1939         }
1940         return array($this->getAttribute('id'), $result);
1941     }
1943     /**
1944      * @param mixed $element
1945      * @return array
1946      */
1947     function _getElNamesRecursive($element) {
1948         if (is_string($element)) {
1949             if (!$this->elementExists($element)) {
1950                 return array();
1951             }
1952             $element = $this->getElement($element);
1953         }
1955         if (is_a($element, 'HTML_QuickForm_group')) {
1956             $elsInGroup = $element->getElements();
1957             $elNames = array();
1958             foreach ($elsInGroup as $elInGroup){
1959                 if (is_a($elInGroup, 'HTML_QuickForm_group')) {
1960                     // not sure if this would work - groups nested in groups
1961                     $elNames = array_merge($elNames, $this->_getElNamesRecursive($elInGroup));
1962                 } else {
1963                     $elNames[] = $element->getElementName($elInGroup->getName());
1964                 }
1965             }
1967         } else if (is_a($element, 'HTML_QuickForm_header')) {
1968             return array();
1970         } else if (is_a($element, 'HTML_QuickForm_hidden')) {
1971             return array();
1973         } else if (method_exists($element, 'getPrivateName') &&
1974                 !($element instanceof HTML_QuickForm_advcheckbox)) {
1975             // The advcheckbox element implements a method called getPrivateName,
1976             // but in a way that is not compatible with the generic API, so we
1977             // have to explicitly exclude it.
1978             return array($element->getPrivateName());
1980         } else {
1981             $elNames = array($element->getName());
1982         }
1984         return $elNames;
1985     }
1987     /**
1988      * Adds a dependency for $elementName which will be disabled if $condition is met.
1989      * If $condition = 'notchecked' (default) then the condition is that the $dependentOn element
1990      * is not checked. If $condition = 'checked' then the condition is that the $dependentOn element
1991      * is checked. If $condition is something else (like "eq" for equals) then it is checked to see if the value
1992      * of the $dependentOn element is $condition (such as equal) to $value.
1993      *
1994      * @param string $elementName the name of the element which will be disabled
1995      * @param string $dependentOn the name of the element whose state will be checked for
1996      *                            condition
1997      * @param string $condition the condition to check
1998      * @param mixed $value used in conjunction with condition.
1999      */
2000     function disabledIf($elementName, $dependentOn, $condition = 'notchecked', $value='1'){
2001         if (!array_key_exists($dependentOn, $this->_dependencies)) {
2002             $this->_dependencies[$dependentOn] = array();
2003         }
2004         if (!array_key_exists($condition, $this->_dependencies[$dependentOn])) {
2005             $this->_dependencies[$dependentOn][$condition] = array();
2006         }
2007         if (!array_key_exists($value, $this->_dependencies[$dependentOn][$condition])) {
2008             $this->_dependencies[$dependentOn][$condition][$value] = array();
2009         }
2010         $this->_dependencies[$dependentOn][$condition][$value][] = $elementName;
2011     }
2013     function registerNoSubmitButton($buttonname){
2014         $this->_noSubmitButtons[]=$buttonname;
2015     }
2017     /**
2018      * @param string $buttonname
2019      * @return mixed
2020      */
2021     function isNoSubmitButton($buttonname){
2022         return (array_search($buttonname, $this->_noSubmitButtons)!==FALSE);
2023     }
2025     /**
2026      * @param string $buttonname
2027      */
2028     function _registerCancelButton($addfieldsname){
2029         $this->_cancelButtons[]=$addfieldsname;
2030     }
2031     /**
2032      * Displays elements without HTML input tags.
2033      * This method is different to freeze() in that it makes sure no hidden
2034      * elements are included in the form.
2035      * Note: If you want to make sure the submitted value is ignored, please use setDefaults().
2036      *
2037      * This function also removes all previously defined rules.
2038      *
2039      * @param    mixed   $elementList       array or string of element(s) to be frozen
2040      * @access   public
2041      */
2042     function hardFreeze($elementList=null)
2043     {
2044         if (!isset($elementList)) {
2045             $this->_freezeAll = true;
2046             $elementList = array();
2047         } else {
2048             if (!is_array($elementList)) {
2049                 $elementList = preg_split('/[ ]*,[ ]*/', $elementList);
2050             }
2051             $elementList = array_flip($elementList);
2052         }
2054         foreach (array_keys($this->_elements) as $key) {
2055             $name = $this->_elements[$key]->getName();
2056             if ($this->_freezeAll || isset($elementList[$name])) {
2057                 $this->_elements[$key]->freeze();
2058                 $this->_elements[$key]->setPersistantFreeze(false);
2059                 unset($elementList[$name]);
2061                 // remove all rules
2062                 $this->_rules[$name] = array();
2063                 // if field is required, remove the rule
2064                 $unset = array_search($name, $this->_required);
2065                 if ($unset !== false) {
2066                     unset($this->_required[$unset]);
2067                 }
2068             }
2069         }
2071         if (!empty($elementList)) {
2072             return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Nonexistant element(s): '" . implode("', '", array_keys($elementList)) . "' in HTML_QuickForm::freeze()", 'HTML_QuickForm_Error', true);
2073         }
2074         return true;
2075     }
2076     /**
2077      * Hard freeze all elements in a form except those whose names are in $elementList or hidden elements in a form.
2078      *
2079      * This function also removes all previously defined rules of elements it freezes.
2080      *
2081      * throws   HTML_QuickForm_Error
2082      *
2083      * @param    array   $elementList       array or string of element(s) not to be frozen
2084      * @access   public
2085      */
2086     function hardFreezeAllVisibleExcept($elementList)
2087     {
2088         $elementList = array_flip($elementList);
2089         foreach (array_keys($this->_elements) as $key) {
2090             $name = $this->_elements[$key]->getName();
2091             $type = $this->_elements[$key]->getType();
2093             if ($type == 'hidden'){
2094                 // leave hidden types as they are
2095             } elseif (!isset($elementList[$name])) {
2096                 $this->_elements[$key]->freeze();
2097                 $this->_elements[$key]->setPersistantFreeze(false);
2099                 // remove all rules
2100                 $this->_rules[$name] = array();
2101                 // if field is required, remove the rule
2102                 $unset = array_search($name, $this->_required);
2103                 if ($unset !== false) {
2104                     unset($this->_required[$unset]);
2105                 }
2106             }
2107         }
2108         return true;
2109     }
2110    /**
2111     * Tells whether the form was already submitted
2112     *
2113     * This is useful since the _submitFiles and _submitValues arrays
2114     * may be completely empty after the trackSubmit value is removed.
2115     *
2116     * @access public
2117     * @return bool
2118     */
2119     function isSubmitted()
2120     {
2121         return parent::isSubmitted() && (!$this->isFrozen());
2122     }
2126 /**
2127  * A renderer for MoodleQuickForm that only uses XHTML and CSS and no
2128  * table tags, extends PEAR class HTML_QuickForm_Renderer_Tableless
2129  *
2130  * Stylesheet is part of standard theme and should be automatically included.
2131  *
2132  * @package   moodlecore
2133  * @copyright Jamie Pratt <me@jamiep.org>
2134  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2135  */
2136 class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
2138     /**
2139     * Element template array
2140     * @var      array
2141     * @access   private
2142     */
2143     var $_elementTemplates;
2144     /**
2145     * Template used when opening a hidden fieldset
2146     * (i.e. a fieldset that is opened when there is no header element)
2147     * @var      string
2148     * @access   private
2149     */
2150     var $_openHiddenFieldsetTemplate = "\n\t<fieldset class=\"hidden\"><div>";
2151    /**
2152     * Header Template string
2153     * @var      string
2154     * @access   private
2155     */
2156     var $_headerTemplate =
2157        "\n\t\t<legend class=\"ftoggler\">{header}</legend>\n\t\t<div class=\"advancedbutton\">{advancedimg}{button}</div><div class=\"fcontainer clearfix\">\n\t\t";
2159    /**
2160     * Template used when opening a fieldset
2161     * @var      string
2162     * @access   private
2163     */
2164     var $_openFieldsetTemplate = "\n\t<fieldset class=\"clearfix\" {id}>";
2166     /**
2167     * Template used when closing a fieldset
2168     * @var      string
2169     * @access   private
2170     */
2171     var $_closeFieldsetTemplate = "\n\t\t</div></fieldset>";
2173    /**
2174     * Required Note template string
2175     * @var      string
2176     * @access   private
2177     */
2178     var $_requiredNoteTemplate =
2179         "\n\t\t<div class=\"fdescription required\">{requiredNote}</div>";
2181     var $_advancedElements = array();
2183     /**
2184      * Whether to display advanced elements (on page load)
2185      *
2186      * @var integer 1 means show 0 means hide
2187      */
2188     var $_showAdvanced;
2190     function MoodleQuickForm_Renderer(){
2191         // switch next two lines for ol li containers for form items.
2192         //        $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>');
2193         $this->_elementTemplates = array(
2194         'default'=>"\n\t\t".'<div class="fitem {advanced}<!-- BEGIN required --> required<!-- END required -->"><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>',
2196         'fieldset'=>"\n\t\t".'<div class="fitem {advanced}<!-- BEGIN required --> required<!-- END required -->"><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>',
2198         '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>',
2200 'warning'=>"\n\t\t".'<div class="fitem {advanced}">{element}</div>',
2202         'nodisplay'=>'');
2204         parent::HTML_QuickForm_Renderer_Tableless();
2205     }
2207     /**
2208      * @param array $elements
2209      */
2210     function setAdvancedElements($elements){
2211         $this->_advancedElements = $elements;
2212     }
2214     /**
2215      * What to do when starting the form
2216      *
2217      * @param object $form MoodleQuickForm
2218      */
2219     function startForm(&$form){
2220         $this->_reqHTML = $form->getReqHTML();
2221         $this->_elementTemplates = str_replace('{req}', $this->_reqHTML, $this->_elementTemplates);
2222         $this->_advancedHTML = $form->getAdvancedHTML();
2223         $this->_showAdvanced = $form->getShowAdvanced();
2224         parent::startForm($form);
2225         if ($form->isFrozen()){
2226             $this->_formTemplate = "\n<div class=\"mform frozen\">\n{content}\n</div>";
2227         } else {
2228             $this->_formTemplate = "\n<form{attributes}>\n\t<div style=\"display: none;\">{hidden}</div>\n{content}\n</form>";
2229             $this->_hiddenHtml .= $form->_pageparams;
2230         }
2233     }
2235     /**
2236      * @param object $group Passed by reference
2237      * @param mixed $required
2238      * @param mixed $error
2239      */
2240     function startGroup(&$group, $required, $error){
2241         if (method_exists($group, 'getElementTemplateType')){
2242             $html = $this->_elementTemplates[$group->getElementTemplateType()];
2243         }else{
2244             $html = $this->_elementTemplates['default'];
2246         }
2247         if ($this->_showAdvanced){
2248             $advclass = ' advanced';
2249         } else {
2250             $advclass = ' advanced hide';
2251         }
2252         if (isset($this->_advancedElements[$group->getName()])){
2253             $html =str_replace(' {advanced}', $advclass, $html);
2254             $html =str_replace('{advancedimg}', $this->_advancedHTML, $html);
2255         } else {
2256             $html =str_replace(' {advanced}', '', $html);
2257             $html =str_replace('{advancedimg}', '', $html);
2258         }
2259         if (method_exists($group, 'getHelpButton')){
2260             $html =str_replace('{help}', $group->getHelpButton(), $html);
2261         }else{
2262             $html =str_replace('{help}', '', $html);
2263         }
2264         $html =str_replace('{name}', $group->getName(), $html);
2265         $html =str_replace('{type}', 'fgroup', $html);
2267         $this->_templates[$group->getName()]=$html;
2268         // Fix for bug in tableless quickforms that didn't allow you to stop a
2269         // fieldset before a group of elements.
2270         // if the element name indicates the end of a fieldset, close the fieldset
2271         if (   in_array($group->getName(), $this->_stopFieldsetElements)
2272             && $this->_fieldsetsOpen > 0
2273            ) {
2274             $this->_html .= $this->_closeFieldsetTemplate;
2275             $this->_fieldsetsOpen--;
2276         }
2277         parent::startGroup($group, $required, $error);
2278     }
2279     /**
2280      * @param object $element
2281      * @param mixed $required
2282      * @param mixed $error
2283      */
2284     function renderElement(&$element, $required, $error){
2285         //manipulate id of all elements before rendering
2286         if (!is_null($element->getAttribute('id'))) {
2287             $id = $element->getAttribute('id');
2288         } else {
2289             $id = $element->getName();
2290         }
2291         //strip qf_ prefix and replace '[' with '_' and strip ']'
2292         $id = preg_replace(array('/^qf_|\]/', '/\[/'), array('', '_'), $id);
2293         if (strpos($id, 'id_') !== 0){
2294             $element->updateAttributes(array('id'=>'id_'.$id));
2295         }
2297         //adding stuff to place holders in template
2298         //check if this is a group element first
2299         if (($this->_inGroup) and !empty($this->_groupElementTemplate)) {
2300             // so it gets substitutions for *each* element
2301             $html = $this->_groupElementTemplate;
2302         }
2303         elseif (method_exists($element, 'getElementTemplateType')){
2304             $html = $this->_elementTemplates[$element->getElementTemplateType()];
2305         }else{
2306             $html = $this->_elementTemplates['default'];
2307         }
2308         if ($this->_showAdvanced){
2309             $advclass = ' advanced';
2310         } else {
2311             $advclass = ' advanced hide';
2312         }
2313         if (isset($this->_advancedElements[$element->getName()])){
2314             $html =str_replace(' {advanced}', $advclass, $html);
2315         } else {
2316             $html =str_replace(' {advanced}', '', $html);
2317         }
2318         if (isset($this->_advancedElements[$element->getName()])||$element->getName() == 'mform_showadvanced'){
2319             $html =str_replace('{advancedimg}', $this->_advancedHTML, $html);
2320         } else {
2321             $html =str_replace('{advancedimg}', '', $html);
2322         }
2323         $html =str_replace('{type}', 'f'.$element->getType(), $html);
2324         $html =str_replace('{name}', $element->getName(), $html);
2325         if (method_exists($element, 'getHelpButton')){
2326             $html = str_replace('{help}', $element->getHelpButton(), $html);
2327         }else{
2328             $html = str_replace('{help}', '', $html);
2330         }
2331         if (($this->_inGroup) and !empty($this->_groupElementTemplate)) {
2332             $this->_groupElementTemplate = $html;
2333         }
2334         elseif (!isset($this->_templates[$element->getName()])) {
2335             $this->_templates[$element->getName()] = $html;
2336         }
2338         parent::renderElement($element, $required, $error);
2339     }
2341     /**
2342      * @global moodle_page $PAGE
2343      * @param object $form Passed by reference
2344      */
2345     function finishForm(&$form){
2346         global $PAGE;
2347         if ($form->isFrozen()){
2348             $this->_hiddenHtml = '';
2349         }
2350         parent::finishForm($form);
2351         if (!$form->isFrozen()) {
2352             $args = $form->getLockOptionObject();
2353             if (count($args[1]) > 0) {
2354                 $PAGE->requires->js_init_call('M.form.initFormDependencies', $args, true, moodleform::get_js_module());
2355             }
2356         }
2357     }
2358    /**
2359     * Called when visiting a header element
2360     *
2361     * @param    object  $header   An HTML_QuickForm_header element being visited
2362     * @access   public
2363     * @return   void
2364     * @global moodle_page $PAGE
2365     */
2366     function renderHeader(&$header) {
2367         global $PAGE;
2369         $name = $header->getName();
2371         $id = empty($name) ? '' : ' id="' . $name . '"';
2372         $id = preg_replace(array('/\]/', '/\[/'), array('', '_'), $id);
2373         if (is_null($header->_text)) {
2374             $header_html = '';
2375         } elseif (!empty($name) && isset($this->_templates[$name])) {
2376             $header_html = str_replace('{header}', $header->toHtml(), $this->_templates[$name]);
2377         } else {
2378             $header_html = str_replace('{header}', $header->toHtml(), $this->_headerTemplate);
2379         }
2381         if (isset($this->_advancedElements[$name])){
2382             $header_html =str_replace('{advancedimg}', $this->_advancedHTML, $header_html);
2383             $elementName='mform_showadvanced';
2384             if ($this->_showAdvanced==0){
2385                 $buttonlabel = get_string('showadvanced', 'form');
2386             } else {
2387                 $buttonlabel = get_string('hideadvanced', 'form');
2388             }
2389             $button = '<input name="'.$elementName.'" class="showadvancedbtn" value="'.$buttonlabel.'" type="submit" />';
2390             $PAGE->requires->js_init_call('M.form.initShowAdvanced', array(), false, moodleform::get_js_module());
2391             $header_html = str_replace('{button}', $button, $header_html);
2392         } else {
2393             $header_html =str_replace('{advancedimg}', '', $header_html);
2394             $header_html = str_replace('{button}', '', $header_html);
2395         }
2397         if ($this->_fieldsetsOpen > 0) {
2398             $this->_html .= $this->_closeFieldsetTemplate;
2399             $this->_fieldsetsOpen--;
2400         }
2402         $openFieldsetTemplate = str_replace('{id}', $id, $this->_openFieldsetTemplate);
2403         if ($this->_showAdvanced){
2404             $advclass = ' class="advanced"';
2405         } else {
2406             $advclass = ' class="advanced hide"';
2407         }
2408         if (isset($this->_advancedElements[$name])){
2409             $openFieldsetTemplate = str_replace('{advancedclass}', $advclass, $openFieldsetTemplate);
2410         } else {
2411             $openFieldsetTemplate = str_replace('{advancedclass}', '', $openFieldsetTemplate);
2412         }
2413         $this->_html .= $openFieldsetTemplate . $header_html;
2414         $this->_fieldsetsOpen++;
2415     } // end func renderHeader
2417     function getStopFieldsetElements(){
2418         return $this->_stopFieldsetElements;
2419     }
2422 /**
2423  * Required elements validation
2424  * This class overrides QuickForm validation since it allowed space or empty tag as a value
2425  */
2426 class MoodleQuickForm_Rule_Required extends HTML_QuickForm_Rule {
2427     /**
2428      * Checks if an element is not empty.
2429      * This is a server-side validation, it works for both text fields and editor fields
2430      *
2431      * @param     string    $value      Value to check
2432      * @param     mixed     $options    Not used yet
2433      * @return    boolean   true if value is not empty
2434      */
2435     function validate($value, $options = null) {
2436         global $CFG;
2437         if (is_array($value) && array_key_exists('text', $value)) {
2438             $value = $value['text'];
2439         }
2440         $stripvalues = array(
2441             '#</?(?!img|canvas|hr).*?>#im', // all tags except img, canvas and hr
2442             '#(\xc2|\xa0|\s|&nbsp;)#', //any whitespaces actually
2443         );
2444         if (!empty($CFG->strictformsrequired)) {
2445             $value = preg_replace($stripvalues, '', (string)$value);
2446         }
2447         if ((string)$value == '') {
2448             return false;
2449         }
2450         return true;
2451     }
2453     /**
2454      * This function returns Javascript code used to build client-side validation.
2455      * It checks if an element is not empty.
2456      *
2457      * @param int $format
2458      * @return array
2459      */
2460     function getValidationScript($format = null) {
2461         global $CFG;
2462         if (!empty($CFG->strictformsrequired)) {
2463             if (!empty($format) && $format == FORMAT_HTML) {
2464                 return array('', "{jsVar}.replace(/(<[^img|hr|canvas]+>)|&nbsp;|\s+/ig, '') == ''");
2465             } else {
2466                 return array('', "{jsVar}.replace(/^\s+$/g, '') == ''");
2467             }
2468         } else {
2469             return array('', "{jsVar} == ''");
2470         }
2471     }
2474 /**
2475  * @global object $GLOBALS['_HTML_QuickForm_default_renderer']
2476  * @name $_HTML_QuickForm_default_renderer
2477  */
2478 $GLOBALS['_HTML_QuickForm_default_renderer'] = new MoodleQuickForm_Renderer();
2480 /** Please keep this list in alphabetical order. */
2481 MoodleQuickForm::registerElementType('advcheckbox', "$CFG->libdir/form/advcheckbox.php", 'MoodleQuickForm_advcheckbox');
2482 MoodleQuickForm::registerElementType('button', "$CFG->libdir/form/button.php", 'MoodleQuickForm_button');
2483 MoodleQuickForm::registerElementType('cancel', "$CFG->libdir/form/cancel.php", 'MoodleQuickForm_cancel');
2484 MoodleQuickForm::registerElementType('searchableselector', "$CFG->libdir/form/searchableselector.php", 'MoodleQuickForm_searchableselector');
2485 MoodleQuickForm::registerElementType('checkbox', "$CFG->libdir/form/checkbox.php", 'MoodleQuickForm_checkbox');
2486 MoodleQuickForm::registerElementType('date_selector', "$CFG->libdir/form/dateselector.php", 'MoodleQuickForm_date_selector');
2487 MoodleQuickForm::registerElementType('date_time_selector', "$CFG->libdir/form/datetimeselector.php", 'MoodleQuickForm_date_time_selector');
2488 MoodleQuickForm::registerElementType('duration', "$CFG->libdir/form/duration.php", 'MoodleQuickForm_duration');
2489 MoodleQuickForm::registerElementType('editor', "$CFG->libdir/form/editor.php", 'MoodleQuickForm_editor');
2490 MoodleQuickForm::registerElementType('file', "$CFG->libdir/form/file.php", 'MoodleQuickForm_file');
2491 MoodleQuickForm::registerElementType('filemanager', "$CFG->libdir/form/filemanager.php", 'MoodleQuickForm_filemanager');
2492 MoodleQuickForm::registerElementType('filepicker', "$CFG->libdir/form/filepicker.php", 'MoodleQuickForm_filepicker');
2493 MoodleQuickForm::registerElementType('format', "$CFG->libdir/form/format.php", 'MoodleQuickForm_format');
2494 MoodleQuickForm::registerElementType('grading', "$CFG->libdir/form/grading.php", 'MoodleQuickForm_grading');
2495 MoodleQuickForm::registerElementType('group', "$CFG->libdir/form/group.php", 'MoodleQuickForm_group');
2496 MoodleQuickForm::registerElementType('header', "$CFG->libdir/form/header.php", 'MoodleQuickForm_header');
2497 MoodleQuickForm::registerElementType('hidden', "$CFG->libdir/form/hidden.php", 'MoodleQuickForm_hidden');
2498 MoodleQuickForm::registerElementType('htmleditor', "$CFG->libdir/form/htmleditor.php", 'MoodleQuickForm_htmleditor');
2499 MoodleQuickForm::registerElementType('modgrade', "$CFG->libdir/form/modgrade.php", 'MoodleQuickForm_modgrade');
2500 MoodleQuickForm::registerElementType('modvisible', "$CFG->libdir/form/modvisible.php", 'MoodleQuickForm_modvisible');
2501 MoodleQuickForm::registerElementType('password', "$CFG->libdir/form/password.php", 'MoodleQuickForm_password');
2502 MoodleQuickForm::registerElementType('passwordunmask', "$CFG->libdir/form/passwordunmask.php", 'MoodleQuickForm_passwordunmask');
2503 MoodleQuickForm::registerElementType('questioncategory', "$CFG->libdir/form/questioncategory.php", 'MoodleQuickForm_questioncategory');
2504 MoodleQuickForm::registerElementType('radio', "$CFG->libdir/form/radio.php", 'MoodleQuickForm_radio');
2505 MoodleQuickForm::registerElementType('recaptcha', "$CFG->libdir/form/recaptcha.php", 'MoodleQuickForm_recaptcha');
2506 MoodleQuickForm::registerElementType('select', "$CFG->libdir/form/select.php", 'MoodleQuickForm_select');
2507 MoodleQuickForm::registerElementType('selectgroups', "$CFG->libdir/form/selectgroups.php", 'MoodleQuickForm_selectgroups');
2508 MoodleQuickForm::registerElementType('selectwithlink', "$CFG->libdir/form/selectwithlink.php", 'MoodleQuickForm_selectwithlink');
2509 MoodleQuickForm::registerElementType('selectyesno', "$CFG->libdir/form/selectyesno.php", 'MoodleQuickForm_selectyesno');
2510 MoodleQuickForm::registerElementType('static', "$CFG->libdir/form/static.php", 'MoodleQuickForm_static');
2511 MoodleQuickForm::registerElementType('submit', "$CFG->libdir/form/submit.php", 'MoodleQuickForm_submit');
2512 MoodleQuickForm::registerElementType('submitlink', "$CFG->libdir/form/submitlink.php", 'MoodleQuickForm_submitlink');
2513 MoodleQuickForm::registerElementType('tags', "$CFG->libdir/form/tags.php", 'MoodleQuickForm_tags');
2514 MoodleQuickForm::registerElementType('text', "$CFG->libdir/form/text.php", 'MoodleQuickForm_text');
2515 MoodleQuickForm::registerElementType('textarea', "$CFG->libdir/form/textarea.php", 'MoodleQuickForm_textarea');
2516 MoodleQuickForm::registerElementType('url', "$CFG->libdir/form/url.php", 'MoodleQuickForm_url');
2517 MoodleQuickForm::registerElementType('warning', "$CFG->libdir/form/warning.php", 'MoodleQuickForm_warning');
2519 MoodleQuickForm::registerRule('required', null, 'MoodleQuickForm_Rule_Required', "$CFG->libdir/formslib.php");