~ubuntu-branches/ubuntu/trusty/moodle/trusty-proposed

« back to all changes in this revision

Viewing changes to .pc/0017-MDL-32785-lib-Ignorning-hard-frozen-fields-when-subm.patch/lib/formslib.php

  • Committer: Package Import Robot
  • Author(s): Didier Raboud
  • Date: 2012-11-12 10:00:00 UTC
  • Revision ID: package-import@ubuntu.com-20121112100000-4c9fw4r1olbshfr9
Tags: 2.2.3.dfsg-2.6
* Non-maintainer upload.

* Backport multiple security issues from upstream's MOODLE_22_STABLE
  branch.
  - MSA-12-0057: MDL-29872 - Access issue through repository
    Fixes CVE-2012-5471
  - MSA-12-0058: MDL-32785 - Possible form data manipulation issue
    Fixes CVE-2012-5472
  - MSA-12-0059: MDL-34448 - Information leak in Database activity module
    Fixes CVE-2012-5473
  - MSA-12-0061: MDL-33791 - Remote code execution through Portfolio API
    Fixes CVE-2012-5479
  - MSA-12-0062: MDL-35558 - Information leak in Database activity module
    Fixes CVE-2012-5480

Show diffs side-by-side

added added

removed removed

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