Updated the HEAD build version to 20090525
[moodle.git] / lib / formslib.php
CommitLineData
2ae22002 1<?php // $Id$
da6f8763 2/**
3 * formslib.php - library of classes for creating forms in Moodle, based on PEAR QuickForms.
da1320da 4 *
5 * To use formslib then you will want to create a new file purpose_form.php eg. edit_form.php
6 * and you want to name your class something like {modulename}_{purpose}_form. Your class will
7 * extend moodleform overriding abstract classes definition and optionally defintion_after_data
8 * and validation.
9 *
10 * See examples of use of this library in course/edit.php and course/edit_form.php
11 *
12 * A few notes :
13 * form defintion is used for both printing of form and processing and should be the same
14 * for both or you may lose some submitted data which won't be let through.
15 * you should be using setType for every form element except select, radio or checkbox
16 * elements, these elements clean themselves.
17 *
18 *
da6f8763 19 * @author Jamie Pratt
da6f8763 20 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
21 */
22
7fc1a27d 23//setup.php icludes our hacked pear libs first
da6f8763 24require_once 'HTML/QuickForm.php';
25require_once 'HTML/QuickForm/DHTMLRulesTableless.php';
26require_once 'HTML/QuickForm/Renderer/Tableless.php';
27
a83ad946 28require_once $CFG->libdir.'/filelib.php';
f6ac3e0a 29require_once $CFG->libdir.'/uploadlib.php'; // TODO: remove
49292f8c 30
832e13f1 31define('EDITOR_UNLIMITED_FILES', -1);
32
a23f0aaf 33/**
34 * Callback called when PEAR throws an error
35 *
36 * @param PEAR_Error $error
37 */
38function pear_handle_error($error){
39 echo '<strong>'.$error->GetMessage().'</strong> '.$error->getUserInfo();
40 echo '<br /> <strong>Backtrace </strong>:';
41 print_object($error->backtrace);
42}
43
864cc1de 44if ($CFG->debug >= DEBUG_ALL){
a23f0aaf 45 PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'pear_handle_error');
864cc1de 46}
47
8e7cebb0 48function form_init_date_js() {
49 global $CFG;
50 static $done = false;
51 if (!$done) {
b1ba4d22 52 //TODO: this is NOT xhtml strict, we will need something like require_css()
8e7cebb0 53 echo '<style type="text/css">';
b1ba4d22 54 echo '@import "' . $CFG->httpswwwroot . '/lib/yui/assets/skins/sam/calendar.css";';
8e7cebb0 55 echo '</style>';
56 require_js(array('yui_yahoo', 'yui_dom', 'yui_event', 'yui_calendar', 'yui_container'));
57 print_delayed_js_call(1, 'init_date_selectors', array(get_string('firstdayofweek')));
58 $done = true;
59 }
60}
f07b9627 61
05f5c40c 62/**
da1320da 63 * Moodle specific wrapper that separates quickforms syntax from moodle code. You won't directly
6073a598 64 * use this class you should write a class definition which extends this class or a more specific
da1320da 65 * subclass such a moodleform_mod for each form you want to display and/or process with formslib.
66 *
67 * You will write your own definition() method which performs the form set up.
05f5c40c 68 */
7f40a229 69class moodleform {
172dd12c 70 protected $_formname; // form name
3c7656b4 71 /**
72 * quickform object definition
73 *
74 * @var MoodleQuickForm
75 */
172dd12c 76 protected $_form;
3c7656b4 77 /**
78 * globals workaround
79 *
80 * @var array
81 */
172dd12c 82 protected $_customdata;
4f51f48f 83 /**
84 * definition_after_data executed flag
85 * @var definition_finalized
86 */
172dd12c 87 protected $_definition_finalized = false;
ebd3c7ac 88
da1320da 89 /**
90 * The constructor function calls the abstract function definition() and it will then
91 * process and clean and attempt to validate incoming data.
92 *
93 * It will call your custom validate method to validate data and will also check any rules
94 * you have specified in definition using addRule
95 *
96 * The name of the form (id attribute of the form) is automatically generated depending on
97 * the name you gave the class extending moodleform. You should call your class something
98 * like
99 *
4f51f48f 100 * @param mixed $action the action attribute for the form. If empty defaults to auto detect the
101 * current url. If a moodle_url object then outputs params as hidden variables.
da1320da 102 * @param array $customdata if your form defintion method needs access to data such as $course
103 * $cm, etc. to construct the form definition then pass it in this array. You can
104 * use globals for somethings.
105 * @param string $method if you set this to anything other than 'post' then _GET and _POST will
106 * be merged and used as incoming data to the form.
107 * @param string $target target frame for form submission. You will rarely use this. Don't use
108 * it if you don't need to as the target attribute is deprecated in xhtml
109 * strict.
110 * @param mixed $attributes you can pass a string of html attributes here or an array.
111 * @return moodleform
112 */
4f51f48f 113 function moodleform($action=null, $customdata=null, $method='post', $target='', $attributes=null, $editable=true) {
a23f0aaf 114 if (empty($action)){
115 $action = strip_querystring(qualified_me());
116 }
f07b9627 117
72f46d11 118 $this->_formname = get_class($this); // '_form' suffix kept in order to prevent collisions of form id and other element
7f40a229 119 $this->_customdata = $customdata;
5bc97c98 120 $this->_form =& new MoodleQuickForm($this->_formname, $method, $action, $target, $attributes);
4f51f48f 121 if (!$editable){
122 $this->_form->hardFreeze();
123 }
7f40a229 124
125 $this->definition();
126
127 $this->_form->addElement('hidden', 'sesskey', null); // automatic sesskey protection
128 $this->_form->setDefault('sesskey', sesskey());
5bc97c98 129 $this->_form->addElement('hidden', '_qf__'.$this->_formname, null); // form submission marker
130 $this->_form->setDefault('_qf__'.$this->_formname, 1);
131 $this->_form->_setDefaultRuleMessages();
7f40a229 132
133 // we have to know all input types before processing submission ;-)
134 $this->_process_submission($method);
7f40a229 135 }
05f5c40c 136
2c412890 137 /**
da1320da 138 * To autofocus on first form element or first element with error.
2c412890 139 *
8dec2253 140 * @param string $name if this is set then the focus is forced to a field with this name
141 *
2c412890 142 * @return string javascript to select form element with first error or
da1320da 143 * first element if no errors. Use this as a parameter
144 * when calling print_header
2c412890 145 */
46f3921e 146 function focus($name=NULL) {
9403060a 147 $form =& $this->_form;
46f3921e 148 $elkeys = array_keys($form->_elementIndex);
149 $error = false;
9403060a 150 if (isset($form->_errors) && 0 != count($form->_errors)){
151 $errorkeys = array_keys($form->_errors);
152 $elkeys = array_intersect($elkeys, $errorkeys);
46f3921e 153 $error = true;
2c412890 154 }
46f3921e 155
156 if ($error or empty($name)) {
157 $names = array();
158 while (empty($names) and !empty($elkeys)) {
159 $el = array_shift($elkeys);
160 $names = $form->_getElNamesRecursive($el);
161 }
162 if (!empty($names)) {
163 $name = array_shift($names);
164 }
8dec2253 165 }
46f3921e 166
167 $focus = '';
168 if (!empty($name)) {
169 $focus = 'forms[\''.$form->getAttribute('id').'\'].elements[\''.$name.'\']';
9403060a 170 }
46f3921e 171
9403060a 172 return $focus;
173 }
7f40a229 174
05f5c40c 175 /**
176 * Internal method. Alters submitted data to be suitable for quickforms processing.
177 * Must be called when the form is fully set up.
178 */
7f40a229 179 function _process_submission($method) {
180 $submission = array();
181 if ($method == 'post') {
182 if (!empty($_POST)) {
183 $submission = $_POST;
184 }
185 } else {
186 $submission = array_merge_recursive($_GET, $_POST); // emulate handling of parameters in xxxx_param()
187 }
188
189 // following trick is needed to enable proper sesskey checks when using GET forms
5bc97c98 190 // the _qf__.$this->_formname serves as a marker that form was actually submitted
191 if (array_key_exists('_qf__'.$this->_formname, $submission) and $submission['_qf__'.$this->_formname] == 1) {
7f40a229 192 if (!confirm_sesskey()) {
c3f9ee10 193 print_error('invalidsesskey');
7f40a229 194 }
05f5c40c 195 $files = $_FILES;
7f40a229 196 } else {
197 $submission = array();
05f5c40c 198 $files = array();
7f40a229 199 }
200
05f5c40c 201 $this->_form->updateSubmission($submission, $files);
7f40a229 202 }
203
05f5c40c 204 /**
4287fc0d 205 * Internal method. Validates all old-style uploaded files.
05f5c40c 206 */
89489cfe 207 function _validate_files(&$files) {
172dd12c 208 global $CFG, $COURSE;
209
89489cfe 210 $files = array();
211
49292f8c 212 if (empty($_FILES)) {
213 // we do not need to do any checks because no files were submitted
89489cfe 214 // note: server side rules do not work for files - use custom verification in validate() instead
49292f8c 215 return true;
216 }
49292f8c 217
172dd12c 218 $errors = array();
219 $filenames = array();
49292f8c 220
221 // now check that we really want each file
222 foreach ($_FILES as $elname=>$file) {
172dd12c 223 $required = $this->_form->isElementRequired($elname);
89489cfe 224
172dd12c 225 if ($file['error'] == 4 and $file['size'] == 0) {
226 if ($required) {
227 $errors[$elname] = get_string('required');
49292f8c 228 }
172dd12c 229 unset($_FILES[$elname]);
230 continue;
231 }
232
a83ad946 233 if (!empty($file['error'])) {
234 $errors[$elname] = file_get_upload_error($file['error']);
172dd12c 235 unset($_FILES[$elname]);
236 continue;
237 }
238
239 if (!is_uploaded_file($file['tmp_name'])) {
240 // TODO: improve error message
241 $errors[$elname] = get_string('error');
242 unset($_FILES[$elname]);
243 continue;
244 }
245
246 if (!$this->_form->elementExists($elname) or !$this->_form->getElementType($elname)=='file') {
247 // hmm, this file was not requested
248 unset($_FILES[$elname]);
249 continue;
250 }
251
252/*
253 // TODO: rethink the file scanning
254 if ($CFG->runclamonupload) {
255 if (!clam_scan_moodle_file($_FILES[$elname], $COURSE)) {
256 $errors[$elname] = $_FILES[$elname]['uploadlog'];
257 unset($_FILES[$elname]);
258 continue;
259 }
260 }
261*/
262 $filename = clean_param($_FILES[$elname]['name'], PARAM_FILE);
263 if ($filename === '') {
264 // TODO: improve error message - wrong chars
265 $errors[$elname] = get_string('error');
266 unset($_FILES[$elname]);
267 continue;
49292f8c 268 }
172dd12c 269 if (in_array($filename, $filenames)) {
270 // TODO: improve error message - duplicate name
271 $errors[$elname] = get_string('error');
272 unset($_FILES[$elname]);
273 continue;
274 }
275 $filenames[] = $filename;
276 $_FILES[$elname]['name'] = $filename;
277
278 $files[$elname] = $_FILES[$elname]['tmp_name'];
49292f8c 279 }
280
281 // return errors if found
172dd12c 282 if (count($errors) == 0){
49292f8c 283 return true;
89489cfe 284
49292f8c 285 } else {
89489cfe 286 $files = array();
49292f8c 287 return $errors;
288 }
289 }
290
05f5c40c 291 /**
da1320da 292 * Load in existing data as form defaults. Usually new entry defaults are stored directly in
293 * form definition (new entry form); this function is used to load in data where values
294 * already exist and data is being edited (edit entry form).
05f5c40c 295 *
294ce987 296 * note: $slashed param removed
297 *
05f5c40c 298 * @param mixed $default_values object or array of default values
299 * @param bool $slased true if magic quotes applied to data values
300 */
294ce987 301 function set_data($default_values) {
7f40a229 302 if (is_object($default_values)) {
303 $default_values = (array)$default_values;
304 }
294ce987 305 $this->_form->setDefaults($default_values);
7f40a229 306 }
307
feaf5d06 308 function set_upload_manager($um=false) {
172dd12c 309 debugging('Not used anymore, please fix code!');
c80a13c7 310 }
311
05f5c40c 312 /**
313 * Check that form was submitted. Does not check validity of submitted data.
314 *
315 * @return bool true if form properly submitted
316 */
7f40a229 317 function is_submitted() {
318 return $this->_form->isSubmitted();
319 }
320
a23f0aaf 321 function no_submit_button_pressed(){
322 static $nosubmit = null; // one check is enough
323 if (!is_null($nosubmit)){
324 return $nosubmit;
325 }
326 $mform =& $this->_form;
327 $nosubmit = false;
f07b9627 328 if (!$this->is_submitted()){
329 return false;
330 }
a23f0aaf 331 foreach ($mform->_noSubmitButtons as $nosubmitbutton){
332 if (optional_param($nosubmitbutton, 0, PARAM_RAW)){
333 $nosubmit = true;
334 break;
335 }
336 }
337 return $nosubmit;
338 }
339
340
05f5c40c 341 /**
342 * Check that form data is valid.
343 *
344 * @return bool true if form data valid
345 */
7f40a229 346 function is_validated() {
49292f8c 347 static $validated = null; // one validation is enough
3ba2c187 348 $mform =& $this->_form;
f07b9627 349
4f51f48f 350 //finalize the form definition before any processing
351 if (!$this->_definition_finalized) {
352 $this->_definition_finalized = true;
353 $this->definition_after_data();
354 }
355
9aa022fe 356 if ($this->no_submit_button_pressed()){
357 return false;
358 } elseif ($validated === null) {
3ba2c187 359 $internal_val = $mform->validate();
89489cfe 360
361 $files = array();
362 $file_val = $this->_validate_files($files);
363 if ($file_val !== true) {
364 if (!empty($file_val)) {
365 foreach ($file_val as $element=>$msg) {
366 $mform->setElementError($element, $msg);
367 }
368 }
369 $file_val = false;
370 }
371
4287fc0d 372 $data = $mform->exportValues();
89489cfe 373 $moodle_val = $this->validation($data, $files);
a78890d5 374 if ((is_array($moodle_val) && count($moodle_val)!==0)) {
375 // non-empty array means errors
376 foreach ($moodle_val as $element=>$msg) {
377 $mform->setElementError($element, $msg);
7f40a229 378 }
a78890d5 379 $moodle_val = false;
380
381 } else {
382 // anything else means validation ok
383 $moodle_val = true;
7f40a229 384 }
89489cfe 385
49292f8c 386 $validated = ($internal_val and $moodle_val and $file_val);
7f40a229 387 }
9aa022fe 388 return $validated;
7f40a229 389 }
390
19110c57 391 /**
392 * Return true if a cancel button has been pressed resulting in the form being submitted.
393 *
394 * @return boolean true if a cancel button has been pressed
395 */
396 function is_cancelled(){
397 $mform =& $this->_form;
a23f0aaf 398 if ($mform->isSubmitted()){
399 foreach ($mform->_cancelButtons as $cancelbutton){
400 if (optional_param($cancelbutton, 0, PARAM_RAW)){
401 return true;
402 }
19110c57 403 }
404 }
405 return false;
406 }
407
05f5c40c 408 /**
da1320da 409 * Return submitted data if properly submitted or returns NULL if validation fails or
410 * if there is no submitted data.
172dd12c 411 *
294ce987 412 * note: $slashed param removed
05f5c40c 413 *
05f5c40c 414 * @return object submitted data; NULL if not valid or not submitted
415 */
294ce987 416 function get_data() {
19110c57 417 $mform =& $this->_form;
3ba2c187 418
7f40a229 419 if ($this->is_submitted() and $this->is_validated()) {
294ce987 420 $data = $mform->exportValues();
5bc97c98 421 unset($data['sesskey']); // we do not need to return sesskey
422 unset($data['_qf__'.$this->_formname]); // we do not need the submission marker too
7f40a229 423 if (empty($data)) {
424 return NULL;
425 } else {
426 return (object)$data;
427 }
428 } else {
429 return NULL;
430 }
431 }
432
4f51f48f 433 /**
434 * Return submitted data without validation or NULL if there is no submitted data.
294ce987 435 * note: $slashed param removed
4f51f48f 436 *
4f51f48f 437 * @return object submitted data; NULL if not submitted
438 */
294ce987 439 function get_submitted_data() {
4f51f48f 440 $mform =& $this->_form;
441
442 if ($this->is_submitted()) {
294ce987 443 $data = $mform->exportValues();
4f51f48f 444 unset($data['sesskey']); // we do not need to return sesskey
445 unset($data['_qf__'.$this->_formname]); // we do not need the submission marker too
446 if (empty($data)) {
447 return NULL;
448 } else {
449 return (object)$data;
450 }
451 } else {
452 return NULL;
453 }
454 }
455
05f5c40c 456 /**
457 * Save verified uploaded files into directory. Upload process can be customised from definition()
172dd12c 458 * NOTE: please use save_stored_file() or save_file()
05f5c40c 459 */
49292f8c 460 function save_files($destination) {
172dd12c 461 debugging('Not used anymore, please fix code! Use save_stored_file() or save_file() instead');
49292f8c 462 return false;
463 }
2b63df96 464
feaf5d06 465 /**
172dd12c 466 * Returns name of uploaded file.
467 * @param string $elname, first element if null
feaf5d06 468 * @return mixed false in case of failure, string if ok
469 */
172dd12c 470 function get_new_filename($elname=null) {
4287fc0d 471 global $USER;
472
172dd12c 473 if (!$this->is_submitted() or !$this->is_validated()) {
474 return false;
475 }
476
477 if (is_null($elname)) {
478 if (empty($_FILES)) {
479 return false;
480 }
481 reset($_FILES);
482 $elname = key($_FILES);
483 }
4287fc0d 484
485 if (empty($elname)) {
486 return false;
487 }
488
489 $element = $this->_form->getElement($elname);
490
491 if ($element instanceof MoodleQuickForm_filepicker) {
492 $values = $this->_form->exportValues($elname);
493 if (empty($values[$elname])) {
494 return false;
495 }
496 $draftid = $values[$elname];
497 $fs = get_file_storage();
498 $context = get_context_instance(CONTEXT_USER, $USER->id);
499 if (!$files = $fs->get_area_files($context->id, 'user_draft', $draftid, 'id DESC', false)) {
500 return false;
501 }
502 $file = reset($files);
503 return $file->get_filename();
504 }
505
172dd12c 506 if (!isset($_FILES[$elname])) {
507 return false;
508 }
509
510 return $_FILES[$elname]['name'];
feaf5d06 511 }
512
b6b1d1ca 513 /**
172dd12c 514 * Save file to standard filesystem
515 * @param string $elname name of element
516 * @param string $pathname full path name of file
517 * @param bool $override override file if exists
518 * @return bool success
b6b1d1ca 519 */
172dd12c 520 function save_file($elname, $pathname, $override=false) {
4287fc0d 521 global $USER;
b6b1d1ca 522
4287fc0d 523 if (!$this->is_submitted() or !$this->is_validated()) {
b6b1d1ca 524 return false;
525 }
526
172dd12c 527 if (file_exists($pathname)) {
528 if ($override) {
529 if (!@unlink($pathname)) {
530 return false;
531 }
532 } else {
533 return false;
534 }
535 }
4287fc0d 536
537 $element = $this->_form->getElement($elname);
538
539 if ($element instanceof MoodleQuickForm_filepicker) {
540 $values = $this->_form->exportValues($elname);
541 if (empty($values[$elname])) {
542 return false;
543 }
544 $draftid = $values[$elname];
545 $fs = get_file_storage();
546 $context = get_context_instance(CONTEXT_USER, $USER->id);
547 if (!$files = $fs->get_area_files($context->id, 'user_draft', $draftid, 'id DESC', false)) {
548 return false;
549 }
550 $file = reset($files);
551
552 return $file->copy_content_to($pathname);
553
554 } else if (isset($_FILES[$elname])) {
555 return copy($_FILES[$elname]['tmp_name'], $pathname);
172dd12c 556 }
557
4287fc0d 558 return false;
172dd12c 559 }
560
561 /**
562 * Save file to local filesystem pool
563 * @param string $elname name of element
924ddb15 564 * @param int $newcontextid
565 * @param string $newfilearea
566 * @param string $newfilepath
567 * @param string $newfilename - use specified filename, if not specified name of uploaded file used
49583e9e 568 * @param bool $overwrite - overwrite file if exists
924ddb15 569 * @param int $newuserid - new userid if required
172dd12c 570 * @return mixed stored_file object or false if error; may throw exception if duplicate found
571 */
f6172562 572 function save_stored_file($elname, $newcontextid, $newfilearea, $newitemid, $newfilepath='/',
49583e9e 573 $newfilename=null, $overwrite=false, $newuserid=null) {
924ddb15 574 global $USER;
575
172dd12c 576 if (!$this->is_submitted() or !$this->is_validated()) {
89489cfe 577 return false;
172dd12c 578 }
89489cfe 579
924ddb15 580 if (empty($newuserid)) {
581 $newuserid = $USER->id;
89489cfe 582 }
b6b1d1ca 583
4287fc0d 584 $element = $this->_form->getElement($elname);
585 $fs = get_file_storage();
172dd12c 586
4287fc0d 587 if ($element instanceof MoodleQuickForm_filepicker) {
588 $values = $this->_form->exportValues($elname);
589 if (empty($values[$elname])) {
590 return false;
591 }
592 $draftid = $values[$elname];
593 $context = get_context_instance(CONTEXT_USER, $USER->id);
594 if (!$files = $fs->get_area_files($context->id, 'user_draft', $draftid, 'id DESC', false)) {
595 return false;
596 }
597 $file = reset($files);
598 if (is_null($newfilename)) {
599 $newfilename = $file->get_filename();
600 }
172dd12c 601
4287fc0d 602 if ($overwrite) {
603 if ($oldfile = $fs->get_file($newcontextid, $newfilearea, $newitemid, $newfilepath, $newfilename)) {
604 if (!$oldfile->delete()) {
605 return false;
606 }
924ddb15 607 }
608 }
609
4287fc0d 610 $file_record = array('contextid'=>$newcontextid, 'filearea'=>$newfilearea, 'itemid'=>$newitemid,
611 'filepath'=>$newfilepath, 'filename'=>$newfilename, 'userid'=>$newuserid);
612 return $fs->create_file_from_storedfile($file_record, $file);
924ddb15 613
4287fc0d 614 } else if (isset($_FILES[$elname])) {
615 $filename = is_null($newfilename) ? $_FILES[$elname]['name'] : $newfilename;
172dd12c 616
4287fc0d 617 if ($overwrite) {
618 if ($oldfile = $fs->get_file($newcontextid, $newfilearea, $newitemid, $newfilepath, $newfilename)) {
619 if (!$oldfile->delete()) {
620 return false;
621 }
622 }
924ddb15 623 }
4287fc0d 624
625 $file_record = array('contextid'=>$newcontextid, 'filearea'=>$newfilearea, 'itemid'=>$newitemid,
626 'filepath'=>$newfilepath, 'filename'=>$newfilename, 'userid'=>$newuserid);
627 return $fs->create_file_from_pathname($file_record, $_FILES[$elname]['tmp_name']);
924ddb15 628 }
629
630 return false;
172dd12c 631 }
632
633 /**
634 * Get content of uploaded file.
635 * @param $element name of file upload element
636 * @return mixed false in case of failure, string if ok
637 */
638 function get_file_content($elname) {
4287fc0d 639 global $USER;
640
172dd12c 641 if (!$this->is_submitted() or !$this->is_validated()) {
642 return false;
643 }
644
4287fc0d 645 $element = $this->_form->getElement($elname);
646
647 if ($element instanceof MoodleQuickForm_filepicker) {
648 $values = $this->_form->exportValues($elname);
649 if (empty($values[$elname])) {
650 return false;
651 }
652 $draftid = $values[$elname];
653 $fs = get_file_storage();
654 $context = get_context_instance(CONTEXT_USER, $USER->id);
655 if (!$files = $fs->get_area_files($context->id, 'user_draft', $draftid, 'id DESC', false)) {
656 return false;
657 }
658 $file = reset($files);
659
660 return $file->get_content();
661
662 } else if (isset($_FILES[$elname])) {
663 return file_get_contents($_FILES[$elname]['tmp_name']);
b6b1d1ca 664 }
172dd12c 665
4287fc0d 666 return false;
b6b1d1ca 667 }
668
05f5c40c 669 /**
670 * Print html form.
671 */
7f40a229 672 function display() {
4f51f48f 673 //finalize the form definition if not yet done
674 if (!$this->_definition_finalized) {
675 $this->_definition_finalized = true;
676 $this->definition_after_data();
677 }
7f40a229 678 $this->_form->display();
679 }
680
49292f8c 681 /**
05f5c40c 682 * Abstract method - always override!
49292f8c 683 *
684 * If you need special handling of uploaded files, create instance of $this->_upload_manager here.
685 */
7f40a229 686 function definition() {
3c0b6b16 687 print_error('mustbeoverriden', 'form', '', get_class($this));
7f40a229 688 }
2c412890 689
c08ac016 690 /**
05f5c40c 691 * Dummy stub method - override if you need to setup the form depending on current
beac4717 692 * values. This method is called after definition(), data submission and set_data().
05f5c40c 693 * All form setup that is dependent on form values should go in here.
c08ac016 694 */
695 function definition_after_data(){
c08ac016 696 }
7f40a229 697
05f5c40c 698 /**
699 * Dummy stub method - override if you needed to perform some extra validation.
700 * If there are errors return array of errors ("fieldname"=>"error message"),
701 * otherwise true if ok.
38f394b2 702 *
89489cfe 703 * Server side rules do not work for uploaded files, implement serverside rules here if needed.
704 *
05f5c40c 705 * @param array $data array of ("fieldname"=>value) of submitted data
89489cfe 706 * @param array $files array of uploaded files "element_name"=>tmp_file_path
a78890d5 707 * @return array of "element_name"=>"error_description" if there are errors,
708 * or an empty array if everything is OK (true allowed for backwards compatibility too).
05f5c40c 709 */
89489cfe 710 function validation($data, $files) {
13ccb7bd 711 return array();
7f40a229 712 }
ebd3c7ac 713
616b549a 714 /**
715 * Method to add a repeating group of elements to a form.
716 *
717 * @param array $elementobjs Array of elements or groups of elements that are to be repeated
718 * @param integer $repeats no of times to repeat elements initially
719 * @param array $options Array of options to apply to elements. Array keys are element names.
720 * This is an array of arrays. The second sets of keys are the option types
721 * for the elements :
722 * 'default' - default value is value
723 * 'type' - PARAM_* constant is value
724 * 'helpbutton' - helpbutton params array is value
725 * 'disabledif' - last three moodleform::disabledIf()
726 * params are value as an array
727 * @param string $repeathiddenname name for hidden element storing no of repeats in this form
728 * @param string $addfieldsname name for button to add more fields
729 * @param int $addfieldsno how many fields to add at a time
271ffe3f 730 * @param string $addstring name of button, {no} is replaced by no of blanks that will be added.
6f3b54c8 731 * @param boolean $addbuttoninside if true, don't call closeHeaderBefore($addfieldsname). Default false.
a23f0aaf 732 * @return int no of repeats of element in this page
616b549a 733 */
6f3b54c8 734 function repeat_elements($elementobjs, $repeats, $options, $repeathiddenname,
735 $addfieldsname, $addfieldsno=5, $addstring=null, $addbuttoninside=false){
271ffe3f 736 if ($addstring===null){
737 $addstring = get_string('addfields', 'form', $addfieldsno);
738 } else {
739 $addstring = str_ireplace('{no}', $addfieldsno, $addstring);
740 }
ebd3c7ac 741 $repeats = optional_param($repeathiddenname, $repeats, PARAM_INT);
742 $addfields = optional_param($addfieldsname, '', PARAM_TEXT);
743 if (!empty($addfields)){
744 $repeats += $addfieldsno;
745 }
ebd3c7ac 746 $mform =& $this->_form;
a23f0aaf 747 $mform->registerNoSubmitButton($addfieldsname);
ebd3c7ac 748 $mform->addElement('hidden', $repeathiddenname, $repeats);
749 //value not to be overridden by submitted value
750 $mform->setConstants(array($repeathiddenname=>$repeats));
414f7bee 751 $namecloned = array();
752 for ($i = 0; $i < $repeats; $i++) {
ebd3c7ac 753 foreach ($elementobjs as $elementobj){
985f0ddd 754 $elementclone = fullclone($elementobj);
7b41a4a9 755 $name = $elementclone->getName();
414f7bee 756 $namecloned[] = $name;
757 if (!empty($name)) {
86aab05c 758 $elementclone->setName($name."[$i]");
759 }
414f7bee 760 if (is_a($elementclone, 'HTML_QuickForm_header')) {
761 $value = $elementclone->_text;
271ffe3f 762 $elementclone->setValue(str_replace('{no}', ($i+1), $value));
763
764 } else {
765 $value=$elementclone->getLabel();
766 $elementclone->setLabel(str_replace('{no}', ($i+1), $value));
ebd3c7ac 767
768 }
7b41a4a9 769
ebd3c7ac 770 $mform->addElement($elementclone);
771 }
772 }
773 for ($i=0; $i<$repeats; $i++) {
774 foreach ($options as $elementname => $elementoptions){
775 $pos=strpos($elementname, '[');
776 if ($pos!==FALSE){
777 $realelementname = substr($elementname, 0, $pos+1)."[$i]";
778 $realelementname .= substr($elementname, $pos+1);
779 }else {
780 $realelementname = $elementname."[$i]";
781 }
782 foreach ($elementoptions as $option => $params){
783
784 switch ($option){
785 case 'default' :
786 $mform->setDefault($realelementname, $params);
787 break;
ebd3c7ac 788 case 'helpbutton' :
789 $mform->setHelpButton($realelementname, $params);
790 break;
791 case 'disabledif' :
414f7bee 792 foreach ($namecloned as $num => $name){
793 if ($params[0] == $name){
794 $params[0] = $params[0]."[$i]";
795 break;
796 }
797 }
9aa022fe 798 $params = array_merge(array($realelementname), $params);
799 call_user_func_array(array(&$mform, 'disabledIf'), $params);
800 break;
801 case 'rule' :
802 if (is_string($params)){
803 $params = array(null, $params, null, 'client');
804 }
805 $params = array_merge(array($realelementname), $params);
806 call_user_func_array(array(&$mform, 'addRule'), $params);
ebd3c7ac 807 break;
808
809 }
810 }
811 }
812 }
271ffe3f 813 $mform->addElement('submit', $addfieldsname, $addstring);
a23f0aaf 814
6f3b54c8 815 if (!$addbuttoninside) {
816 $mform->closeHeaderBefore($addfieldsname);
817 }
ebd3c7ac 818
19194f82 819 return $repeats;
ebd3c7ac 820 }
6073a598 821
822 /**
823 * Adds a link/button that controls the checked state of a group of checkboxes.
824 * @param int $groupid The id of the group of advcheckboxes this element controls
825 * @param string $text The text of the link. Defaults to "select all/none"
826 * @param array $attributes associative array of HTML attributes
827 * @param int $originalValue The original general state of the checkboxes before the user first clicks this element
828 */
172dd12c 829 function add_checkbox_controller($groupid, $buttontext, $attributes, $originalValue = 0) {
6073a598 830 global $CFG;
831 if (empty($text)) {
832 $text = get_string('selectallornone', 'form');
833 }
834
835 $mform = $this->_form;
836 $select_value = optional_param('checkbox_controller'. $groupid, null, PARAM_INT);
837
838 if ($select_value == 0 || is_null($select_value)) {
839 $new_select_value = 1;
840 } else {
841 $new_select_value = 0;
842 }
843
844 $mform->addElement('hidden', "checkbox_controller$groupid");
845 $mform->setConstants(array("checkbox_controller$groupid" => $new_select_value));
172dd12c 846
6073a598 847 // Locate all checkboxes for this group and set their value, IF the optional param was given
848 if (!is_null($select_value)) {
849 foreach ($this->_form->_elements as $element) {
850 if ($element->getAttribute('class') == "checkboxgroup$groupid") {
851 $mform->setConstants(array($element->getAttribute('name') => $select_value));
852 }
853 }
854 }
855
856 $checkbox_controller_name = 'nosubmit_checkbox_controller' . $groupid;
857 $mform->registerNoSubmitButton($checkbox_controller_name);
172dd12c 858
6073a598 859 // Prepare Javascript for submit element
860 $js = "\n//<![CDATA[\n";
861 if (!defined('HTML_QUICKFORM_CHECKBOXCONTROLLER_EXISTS')) {
862 $js .= <<<EOS
863function html_quickform_toggle_checkboxes(group) {
864 var checkboxes = getElementsByClassName(document, 'input', 'checkboxgroup' + group);
865 var newvalue = false;
866 var global = eval('html_quickform_checkboxgroup' + group + ';');
867 if (global == 1) {
172dd12c 868 eval('html_quickform_checkboxgroup' + group + ' = 0;');
6073a598 869 newvalue = '';
870 } else {
172dd12c 871 eval('html_quickform_checkboxgroup' + group + ' = 1;');
6073a598 872 newvalue = 'checked';
873 }
874
875 for (i = 0; i < checkboxes.length; i++) {
172dd12c 876 checkboxes[i].checked = newvalue;
6073a598 877 }
878}
879EOS;
880 define('HTML_QUICKFORM_CHECKBOXCONTROLLER_EXISTS', true);
881 }
882 $js .= "\nvar html_quickform_checkboxgroup$groupid=$originalValue;\n";
172dd12c 883
6073a598 884 $js .= "//]]>\n";
172dd12c 885
6073a598 886 require_once("$CFG->libdir/form/submitlink.php");
887 $submitlink = new MoodleQuickForm_submitlink($checkbox_controller_name, $attributes);
888 $submitlink->_js = $js;
889 $submitlink->_onclick = "html_quickform_toggle_checkboxes($groupid); return false;";
172dd12c 890 $mform->addElement($submitlink);
6073a598 891 $mform->setDefault($checkbox_controller_name, $text);
892 }
893
a23f0aaf 894 /**
1d284fbd 895 * Use this method to a cancel and submit button to the end of your form. Pass a param of false
a23f0aaf 896 * if you don't want a cancel button in your form. If you have a cancel button make sure you
897 * check for it being pressed using is_cancelled() and redirecting if it is true before trying to
beac4717 898 * get data with get_data().
a23f0aaf 899 *
900 * @param boolean $cancel whether to show cancel button, default true
a23f0aaf 901 * @param string $submitlabel label for submit button, defaults to get_string('savechanges')
902 */
1d284fbd 903 function add_action_buttons($cancel = true, $submitlabel=null){
a23f0aaf 904 if (is_null($submitlabel)){
905 $submitlabel = get_string('savechanges');
906 }
907 $mform =& $this->_form;
1d284fbd 908 if ($cancel){
909 //when two elements we need a group
a23f0aaf 910 $buttonarray=array();
911 $buttonarray[] = &$mform->createElement('submit', 'submitbutton', $submitlabel);
1d284fbd 912 $buttonarray[] = &$mform->createElement('cancel');
a23f0aaf 913 $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
4f51f48f 914 $mform->closeHeaderBefore('buttonar');
a23f0aaf 915 } else {
916 //no group needed
917 $mform->addElement('submit', 'submitbutton', $submitlabel);
4f51f48f 918 $mform->closeHeaderBefore('submitbutton');
a23f0aaf 919 }
920 }
7f40a229 921}
922
da1320da 923/**
924 * You never extend this class directly. The class methods of this class are available from
6073a598 925 * the private $this->_form property on moodleform and its children. You generally only
da1320da 926 * call methods on this class from within abstract methods that you override on moodleform such
927 * as definition and definition_after_data
928 *
929 */
7f40a229 930class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
931 var $_types = array();
50ef8eb9 932 var $_dependencies = array();
19110c57 933 /**
934 * Array of buttons that if pressed do not result in the processing of the form.
935 *
936 * @var array
937 */
938 var $_noSubmitButtons=array();
939 /**
940 * Array of buttons that if pressed do not result in the processing of the form.
941 *
942 * @var array
943 */
944 var $_cancelButtons=array();
7f40a229 945
19194f82 946 /**
947 * Array whose keys are element names. If the key exists this is a advanced element
948 *
949 * @var array
950 */
951 var $_advancedElements = array();
952
953 /**
954 * Whether to display advanced elements (on page load)
955 *
956 * @var boolean
957 */
958 var $_showAdvanced = null;
959
f07b9627 960 /**
961 * The form name is derrived from the class name of the wrapper minus the trailing form
962 * It is a name with words joined by underscores whereas the id attribute is words joined by
963 * underscores.
964 *
965 * @var unknown_type
966 */
967 var $_formName = '';
43914931 968
4f51f48f 969 /**
970 * String with the html for hidden params passed in as part of a moodle_url object for the action. Output in the form.
971 *
972 * @var string
973 */
974 var $_pageparams = '';
975
da6f8763 976 /**
977 * Class constructor - same parameters as HTML_QuickForm_DHTMLRulesTableless
978 * @param string $formName Form's name.
979 * @param string $method (optional)Form's method defaults to 'POST'
4f51f48f 980 * @param mixed $action (optional)Form's action - string or moodle_url
da6f8763 981 * @param string $target (optional)Form's target defaults to none
982 * @param mixed $attributes (optional)Extra attributes for <form> tag
983 * @param bool $trackSubmit (optional)Whether to track if the form was submitted by adding a special hidden field
984 * @access public
985 */
7f40a229 986 function MoodleQuickForm($formName, $method, $action, $target='', $attributes=null){
dcf6d93c 987 global $CFG;
988
271ffe3f 989 static $formcounter = 1;
7f40a229 990
da6f8763 991 HTML_Common::HTML_Common($attributes);
da6f8763 992 $target = empty($target) ? array() : array('target' => $target);
f07b9627 993 $this->_formName = $formName;
4f51f48f 994 if (is_a($action, 'moodle_url')){
995 $this->_pageparams = $action->hidden_params_out();
996 $action = $action->out(true);
997 } else {
998 $this->_pageparams = '';
999 }
da6f8763 1000 //no 'name' atttribute for form in xhtml strict :
1327f08e 1001 $attributes = array('action'=>$action, 'method'=>$method,
1002 'accept-charset'=>'utf-8', 'id'=>'mform'.$formcounter) + $target;
271ffe3f 1003 $formcounter++;
da6f8763 1004 $this->updateAttributes($attributes);
da6f8763 1005
7f40a229 1006 //this is custom stuff for Moodle :
da6f8763 1007 $oldclass= $this->getAttribute('class');
1008 if (!empty($oldclass)){
1009 $this->updateAttributes(array('class'=>$oldclass.' mform'));
1010 }else {
80f962df 1011 $this->updateAttributes(array('class'=>'mform'));
da6f8763 1012 }
653f890d 1013 $this->_reqHTML = '<img class="req" title="'.get_string('requiredelement', 'form').'" alt="'.get_string('requiredelement', 'form').'" src="'.$CFG->pixpath.'/req.gif'.'" />';
1014 $this->_advancedHTML = '<img class="adv" title="'.get_string('advancedelement', 'form').'" alt="'.get_string('advancedelement', 'form').'" src="'.$CFG->pixpath.'/adv.gif'.'" />';
5bff0855 1015 $this->setRequiredNote(get_string('somefieldsrequired', 'form', '<img alt="'.get_string('requiredelement', 'form').'" src="'.$CFG->pixpath.'/req.gif'.'" />'));
1016 //(Help file doesn't add anything) helpbutton('requiredelement', get_string('requiredelement', 'form'), 'moodle', true, false, '', true));
19194f82 1017 }
1018
a23f0aaf 1019 /**
1020 * Use this method to indicate an element in a form is an advanced field. If items in a form
1021 * are marked as advanced then 'Hide/Show Advanced' buttons will automatically be displayed in the
1022 * form so the user can decide whether to display advanced form controls.
1023 *
1024 * If you set a header element to advanced then all elements it contains will also be set as advanced.
1025 *
1026 * @param string $elementName group or element name (not the element name of something inside a group).
1027 * @param boolean $advanced default true sets the element to advanced. False removes advanced mark.
1028 */
1029 function setAdvanced($elementName, $advanced=true){
1030 if ($advanced){
1031 $this->_advancedElements[$elementName]='';
1032 } elseif (isset($this->_advancedElements[$elementName])) {
1033 unset($this->_advancedElements[$elementName]);
1034 }
1035 if ($advanced && $this->getElementType('mform_showadvanced_last')===false){
1036 $this->setShowAdvanced();
1037 $this->registerNoSubmitButton('mform_showadvanced');
1038
1039 $this->addElement('hidden', 'mform_showadvanced_last');
1040 }
1041 }
1042 /**
1043 * Set whether to show advanced elements in the form on first displaying form. Default is not to
1044 * display advanced elements in the form until 'Show Advanced' is pressed.
1045 *
1046 * You can get the last state of the form and possibly save it for this user by using
1047 * value 'mform_showadvanced_last' in submitted data.
1048 *
1049 * @param boolean $showadvancedNow
1050 */
1051 function setShowAdvanced($showadvancedNow = null){
1052 if ($showadvancedNow === null){
1053 if ($this->_showAdvanced !== null){
1054 return;
1055 } else { //if setShowAdvanced is called without any preference
1056 //make the default to not show advanced elements.
f07b9627 1057 $showadvancedNow = get_user_preferences(
1058 moodle_strtolower($this->_formName.'_showadvanced', 0));
a23f0aaf 1059 }
a23f0aaf 1060 }
1061 //value of hidden element
1062 $hiddenLast = optional_param('mform_showadvanced_last', -1, PARAM_INT);
1063 //value of button
1064 $buttonPressed = optional_param('mform_showadvanced', 0, PARAM_RAW);
1065 //toggle if button pressed or else stay the same
1066 if ($hiddenLast == -1) {
1067 $next = $showadvancedNow;
1068 } elseif ($buttonPressed) { //toggle on button press
1069 $next = !$hiddenLast;
1070 } else {
1071 $next = $hiddenLast;
1072 }
1073 $this->_showAdvanced = $next;
f07b9627 1074 if ($showadvancedNow != $next){
1075 set_user_preference($this->_formName.'_showadvanced', $next);
1076 }
a23f0aaf 1077 $this->setConstants(array('mform_showadvanced_last'=>$next));
19194f82 1078 }
1079 function getShowAdvanced(){
1080 return $this->_showAdvanced;
1081 }
1082
19194f82 1083
1084 /**
1085 * Accepts a renderer
1086 *
1087 * @param HTML_QuickForm_Renderer An HTML_QuickForm_Renderer object
1088 * @since 3.0
1089 * @access public
1090 * @return void
1091 */
46f3921e 1092 function accept(&$renderer) {
19194f82 1093 if (method_exists($renderer, 'setAdvancedElements')){
1094 //check for visible fieldsets where all elements are advanced
1095 //and mark these headers as advanced as well.
1096 //And mark all elements in a advanced header as advanced
1097 $stopFields = $renderer->getStopFieldSetElements();
1098 $lastHeader = null;
1099 $lastHeaderAdvanced = false;
1100 $anyAdvanced = false;
1101 foreach (array_keys($this->_elements) as $elementIndex){
1102 $element =& $this->_elements[$elementIndex];
46f3921e 1103
1104 // if closing header and any contained element was advanced then mark it as advanced
19194f82 1105 if ($element->getType()=='header' || in_array($element->getName(), $stopFields)){
46f3921e 1106 if ($anyAdvanced && !is_null($lastHeader)){
19194f82 1107 $this->setAdvanced($lastHeader->getName());
1108 }
1109 $lastHeaderAdvanced = false;
46f3921e 1110 unset($lastHeader);
1111 $lastHeader = null;
19194f82 1112 } elseif ($lastHeaderAdvanced) {
1113 $this->setAdvanced($element->getName());
1114 }
46f3921e 1115
19194f82 1116 if ($element->getType()=='header'){
1117 $lastHeader =& $element;
1118 $anyAdvanced = false;
1119 $lastHeaderAdvanced = isset($this->_advancedElements[$element->getName()]);
1120 } elseif (isset($this->_advancedElements[$element->getName()])){
1121 $anyAdvanced = true;
1122 }
1123 }
46f3921e 1124 // the last header may not be closed yet...
1125 if ($anyAdvanced && !is_null($lastHeader)){
1126 $this->setAdvanced($lastHeader->getName());
1127 }
19194f82 1128 $renderer->setAdvancedElements($this->_advancedElements);
19194f82 1129
19194f82 1130 }
1131 parent::accept($renderer);
1132 }
1133
19194f82 1134
19194f82 1135
1136 function closeHeaderBefore($elementName){
1137 $renderer =& $this->defaultRenderer();
1138 $renderer->addStopFieldsetElements($elementName);
da6f8763 1139 }
bb40325e 1140
da1320da 1141 /**
1142 * Should be used for all elements of a form except for select, radio and checkboxes which
1143 * clean their own data.
1144 *
1145 * @param string $elementname
1146 * @param integer $paramtype use the constants PARAM_*.
1147 * * PARAM_CLEAN is deprecated and you should try to use a more specific type.
1148 * * PARAM_TEXT should be used for cleaning data that is expected to be plain text.
1149 * It will strip all html tags. But will still let tags for multilang support
1150 * through.
1151 * * PARAM_RAW means no cleaning whatsoever, it is used mostly for data from the
1152 * html editor. Data from the editor is later cleaned before display using
1153 * format_text() function. PARAM_RAW can also be used for data that is validated
1154 * by some other way or printed by p() or s().
1155 * * PARAM_INT should be used for integers.
1156 * * PARAM_ACTION is an alias of PARAM_ALPHA and is used for hidden fields specifying
1157 * form actions.
1158 */
7f40a229 1159 function setType($elementname, $paramtype) {
1160 $this->_types[$elementname] = $paramtype;
1161 }
49292f8c 1162
da1320da 1163 /**
1164 * See description of setType above. This can be used to set several types at once.
1165 *
1166 * @param array $paramtypes
1167 */
c56f1826 1168 function setTypes($paramtypes) {
1169 $this->_types = $paramtypes + $this->_types;
1170 }
49292f8c 1171
1172 function updateSubmission($submission, $files) {
1173 $this->_flagSubmitted = false;
1174
7f40a229 1175 if (empty($submission)) {
1176 $this->_submitValues = array();
7f40a229 1177 } else {
1178 foreach ($submission as $key=>$s) {
1179 if (array_key_exists($key, $this->_types)) {
1180 $submission[$key] = clean_param($s, $this->_types[$key]);
1181 }
1182 }
294ce987 1183 $this->_submitValues = $submission;
7f40a229 1184 $this->_flagSubmitted = true;
1185 }
1186
49292f8c 1187 if (empty($files)) {
1188 $this->_submitFiles = array();
1189 } else {
49292f8c 1190 $this->_submitFiles = $files;
1191 $this->_flagSubmitted = true;
1192 }
1193
2c412890 1194 // need to tell all elements that they need to update their value attribute.
1195 foreach (array_keys($this->_elements) as $key) {
1196 $this->_elements[$key]->onQuickFormEvent('updateValue', null, $this);
1197 }
7f40a229 1198 }
1199
da6f8763 1200 function getReqHTML(){
1201 return $this->_reqHTML;
1202 }
7f40a229 1203
19194f82 1204 function getAdvancedHTML(){
1205 return $this->_advancedHTML;
1206 }
1207
7f40a229 1208 /**
da1320da 1209 * Initializes a default form value. Used to specify the default for a new entry where
beac4717 1210 * no data is loaded in using moodleform::set_data()
7f40a229 1211 *
294ce987 1212 * note: $slashed param removed
1213 *
7f40a229 1214 * @param string $elementname element name
1215 * @param mixed $values values for that element name
7f40a229 1216 * @access public
1217 * @return void
1218 */
294ce987 1219 function setDefault($elementName, $defaultValue){
1220 $this->setDefaults(array($elementName=>$defaultValue));
7f40a229 1221 } // end func setDefault
da6f8763 1222 /**
c56f1826 1223 * Add an array of buttons to the form
7f40a229 1224 * @param array $buttons An associative array representing help button to attach to
da6f8763 1225 * to the form. keys of array correspond to names of elements in form.
7f40a229 1226 *
da6f8763 1227 * @access public
1228 */
d4fe14d3 1229 function setHelpButtons($buttons, $suppresscheck=false, $function='helpbutton'){
7f40a229 1230
c56f1826 1231 foreach ($buttons as $elementname => $button){
d4fe14d3 1232 $this->setHelpButton($elementname, $button, $suppresscheck, $function);
da6f8763 1233 }
1234 }
c56f1826 1235 /**
da1320da 1236 * Add a single button.
c56f1826 1237 *
1238 * @param string $elementname name of the element to add the item to
d4fe14d3 1239 * @param array $button - arguments to pass to function $function
c56f1826 1240 * @param boolean $suppresscheck - whether to throw an error if the element
1241 * doesn't exist.
d4fe14d3 1242 * @param string $function - function to generate html from the arguments in $button
c56f1826 1243 */
d4fe14d3 1244 function setHelpButton($elementname, $button, $suppresscheck=false, $function='helpbutton'){
c56f1826 1245 if (array_key_exists($elementname, $this->_elementIndex)){
1246 //_elements has a numeric index, this code accesses the elements by name
1247 $element=&$this->_elements[$this->_elementIndex[$elementname]];
1248 if (method_exists($element, 'setHelpButton')){
d4fe14d3 1249 $element->setHelpButton($button, $function);
c56f1826 1250 }else{
1251 $a=new object();
1252 $a->name=$element->getName();
1253 $a->classname=get_class($element);
1254 print_error('nomethodforaddinghelpbutton', 'form', '', $a);
1255 }
1256 }elseif (!$suppresscheck){
1257 print_error('nonexistentformelements', 'form', '', $elementname);
2c412890 1258 }
c56f1826 1259 }
7f40a229 1260
cc444336 1261 /**
1262 * Set constant value not overriden by _POST or _GET
1263 * note: this does not work for complex names with [] :-(
1264 * @param string $elname name of element
1265 * @param mixed $value
1266 * @return void
1267 */
1268 function setConstant($elname, $value) {
1269 $this->_constantValues = HTML_QuickForm::arrayMerge($this->_constantValues, array($elname=>$value));
1270 $element =& $this->getElement($elname);
1271 $element->onQuickFormEvent('updateValue', null, $this);
1272 }
1273
294ce987 1274 function exportValues($elementList = null){
0ffb4cc7 1275 $unfiltered = array();
1276 if (null === $elementList) {
1277 // iterate over all elements, calling their exportValue() methods
98af2d1d 1278 $emptyarray = array();
0ffb4cc7 1279 foreach (array_keys($this->_elements) as $key) {
1280 if ($this->_elements[$key]->isFrozen() && !$this->_elements[$key]->_persistantFreeze){
98af2d1d 1281 $value = $this->_elements[$key]->exportValue($emptyarray, true);
0ffb4cc7 1282 } else {
1283 $value = $this->_elements[$key]->exportValue($this->_submitValues, true);
1284 }
1285
1286 if (is_array($value)) {
1287 // This shit throws a bogus warning in PHP 4.3.x
1288 $unfiltered = HTML_QuickForm::arrayMerge($unfiltered, $value);
1289 }
1290 }
1291 } else {
1292 if (!is_array($elementList)) {
1293 $elementList = array_map('trim', explode(',', $elementList));
1294 }
1295 foreach ($elementList as $elementName) {
1296 $value = $this->exportValue($elementName);
1297 if (PEAR::isError($value)) {
1298 return $value;
1299 }
4287fc0d 1300 //oh, stock QuickFOrm was returning array of arrays!
1301 $unfiltered = HTML_QuickForm::arrayMerge($unfiltered, $value);
0ffb4cc7 1302 }
1303 }
7f40a229 1304
294ce987 1305 return $unfiltered;
da6f8763 1306 }
f07b9627 1307 /**
1308 * Adds a validation rule for the given field
1309 *
1310 * If the element is in fact a group, it will be considered as a whole.
1311 * To validate grouped elements as separated entities,
1312 * use addGroupRule instead of addRule.
1313 *
1314 * @param string $element Form element name
1315 * @param string $message Message to display for invalid data
1316 * @param string $type Rule type, use getRegisteredRules() to get types
1317 * @param string $format (optional)Required for extra rule data
1318 * @param string $validation (optional)Where to perform validation: "server", "client"
1319 * @param boolean $reset Client-side validation: reset the form element to its original value if there is an error?
1320 * @param boolean $force Force the rule to be applied, even if the target form element does not exist
1321 * @since 1.0
1322 * @access public
1323 * @throws HTML_QuickForm_Error
1324 */
1325 function addRule($element, $message, $type, $format=null, $validation='server', $reset = false, $force = false)
1326 {
1327 parent::addRule($element, $message, $type, $format, $validation, $reset, $force);
1328 if ($validation == 'client') {
1329 $this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_formName . '; } catch(e) { return true; } return myValidator(this);'));
1330 }
1331
1332 } // end func addRule
1333 /**
1334 * Adds a validation rule for the given group of elements
1335 *
1336 * Only groups with a name can be assigned a validation rule
1337 * Use addGroupRule when you need to validate elements inside the group.
1338 * Use addRule if you need to validate the group as a whole. In this case,
1339 * the same rule will be applied to all elements in the group.
1340 * Use addRule if you need to validate the group against a function.
1341 *
1342 * @param string $group Form group name
1343 * @param mixed $arg1 Array for multiple elements or error message string for one element
1344 * @param string $type (optional)Rule type use getRegisteredRules() to get types
1345 * @param string $format (optional)Required for extra rule data
1346 * @param int $howmany (optional)How many valid elements should be in the group
1347 * @param string $validation (optional)Where to perform validation: "server", "client"
1348 * @param bool $reset Client-side: whether to reset the element's value to its original state if validation failed.
1349 * @since 2.5
1350 * @access public
1351 * @throws HTML_QuickForm_Error
1352 */
1353 function addGroupRule($group, $arg1, $type='', $format=null, $howmany=0, $validation = 'server', $reset = false)
1354 {
1355 parent::addGroupRule($group, $arg1, $type, $format, $howmany, $validation, $reset);
1356 if (is_array($arg1)) {
3a298174 1357 foreach ($arg1 as $rules) {
f07b9627 1358 foreach ($rules as $rule) {
1359 $validation = (isset($rule[3]) && 'client' == $rule[3])? 'client': 'server';
1360
1361 if ('client' == $validation) {
1362 $this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_formName . '; } catch(e) { return true; } return myValidator(this);'));
1363 }
1364 }
1365 }
1366 } elseif (is_string($arg1)) {
1367
1368 if ($validation == 'client') {
3a298174 1369 $this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_formName . '; } catch(e) { return true; } return myValidator(this);'));
f07b9627 1370 }
1371 }
1372 } // end func addGroupRule
1373
1374 // }}}
5bc97c98 1375 /**
1376 * Returns the client side validation script
1377 *
1378 * The code here was copied from HTML_QuickForm_DHTMLRulesTableless who copied it from HTML_QuickForm
1379 * and slightly modified to run rules per-element
1380 * Needed to override this because of an error with client side validation of grouped elements.
1381 *
1382 * @access public
1383 * @return string Javascript to perform validation, empty string if no 'client' rules were added
1384 */
1385 function getValidationScript()
1386 {
1387 if (empty($this->_rules) || empty($this->_attributes['onsubmit'])) {
1388 return '';
1389 }
1390
1391 include_once('HTML/QuickForm/RuleRegistry.php');
1392 $registry =& HTML_QuickForm_RuleRegistry::singleton();
1393 $test = array();
1394 $js_escape = array(
1395 "\r" => '\r',
1396 "\n" => '\n',
1397 "\t" => '\t',
1398 "'" => "\\'",
1399 '"' => '\"',
1400 '\\' => '\\\\'
1401 );
1402
1403 foreach ($this->_rules as $elementName => $rules) {
1404 foreach ($rules as $rule) {
1405 if ('client' == $rule['validation']) {
da1320da 1406 unset($element); //TODO: find out how to properly initialize it
5bc97c98 1407
1408 $dependent = isset($rule['dependent']) && is_array($rule['dependent']);
1409 $rule['message'] = strtr($rule['message'], $js_escape);
1410
1411 if (isset($rule['group'])) {
1412 $group =& $this->getElement($rule['group']);
1413 // No JavaScript validation for frozen elements
1414 if ($group->isFrozen()) {
1415 continue 2;
1416 }
1417 $elements =& $group->getElements();
1418 foreach (array_keys($elements) as $key) {
1419 if ($elementName == $group->getElementName($key)) {
1420 $element =& $elements[$key];
1421 break;
1422 }
1423 }
1424 } elseif ($dependent) {
1425 $element = array();
1426 $element[] =& $this->getElement($elementName);
3a298174 1427 foreach ($rule['dependent'] as $elName) {
5bc97c98 1428 $element[] =& $this->getElement($elName);
1429 }
1430 } else {
1431 $element =& $this->getElement($elementName);
1432 }
1433 // No JavaScript validation for frozen elements
1434 if (is_object($element) && $element->isFrozen()) {
1435 continue 2;
1436 } elseif (is_array($element)) {
1437 foreach (array_keys($element) as $key) {
1438 if ($element[$key]->isFrozen()) {
1439 continue 3;
1440 }
1441 }
1442 }
1443 // Fix for bug displaying errors for elements in a group
1444 //$test[$elementName][] = $registry->getValidationScript($element, $elementName, $rule);
1445 $test[$elementName][0][] = $registry->getValidationScript($element, $elementName, $rule);
1446 $test[$elementName][1]=$element;
1447 //end of fix
1448 }
1449 }
1450 }
7c77033f 1451
1452 // Fix for MDL-9524. If you don't do this, then $element may be left as a reference to one of the fields in
1453 // the form, and then that form field gets corrupted by the code that follows.
1454 unset($element);
1455
5bc97c98 1456 $js = '
1457<script type="text/javascript">
1458//<![CDATA[
1cbb09f1 1459
1460var skipClientValidation = false;
1461
5bc97c98 1462function qf_errorHandler(element, _qfMsg) {
1463 div = element.parentNode;
1464 if (_qfMsg != \'\') {
e35c9eeb 1465 var errorSpan = document.getElementById(\'id_error_\'+element.name);
e7004d05 1466 if (!errorSpan) {
1467 errorSpan = document.createElement("span");
e35c9eeb 1468 errorSpan.id = \'id_error_\'+element.name;
1469 errorSpan.className = "error";
fed13a5e 1470 element.parentNode.insertBefore(errorSpan, element.parentNode.firstChild);
5bc97c98 1471 }
fed13a5e 1472
e7004d05 1473 while (errorSpan.firstChild) {
1474 errorSpan.removeChild(errorSpan.firstChild);
5bc97c98 1475 }
2c412890 1476
e7004d05 1477 errorSpan.appendChild(document.createTextNode(_qfMsg.substring(3)));
e35c9eeb 1478 errorSpan.appendChild(document.createElement("br"));
5bc97c98 1479
1480 if (div.className.substr(div.className.length - 6, 6) != " error"
1481 && div.className != "error") {
1482 div.className += " error";
1483 }
1484
1485 return false;
1486 } else {
e35c9eeb 1487 var errorSpan = document.getElementById(\'id_error_\'+element.name);
e7004d05 1488 if (errorSpan) {
1489 errorSpan.parentNode.removeChild(errorSpan);
5bc97c98 1490 }
1491
1492 if (div.className.substr(div.className.length - 6, 6) == " error") {
1493 div.className = div.className.substr(0, div.className.length - 6);
1494 } else if (div.className == "error") {
1495 div.className = "";
1496 }
1497
1498 return true;
1499 }
1500}';
1501 $validateJS = '';
1502 foreach ($test as $elementName => $jsandelement) {
1503 // Fix for bug displaying errors for elements in a group
1504 //unset($element);
1505 list($jsArr,$element)=$jsandelement;
1506 //end of fix
1507 $js .= '
f07b9627 1508function validate_' . $this->_formName . '_' . $elementName . '(element) {
5bc97c98 1509 var value = \'\';
1510 var errFlag = new Array();
1511 var _qfGroups = {};
1512 var _qfMsg = \'\';
1513 var frm = element.parentNode;
cd350b53 1514 while (frm && frm.nodeName.toUpperCase() != "FORM") {
5bc97c98 1515 frm = frm.parentNode;
1516 }
1517' . join("\n", $jsArr) . '
1518 return qf_errorHandler(element, _qfMsg);
1519}
1520';
1521 $validateJS .= '
2ef7c374 1522 ret = validate_' . $this->_formName . '_' . $elementName.'(frm.elements[\''.$elementName.'\']) && ret;
1523 if (!ret && !first_focus) {
1524 first_focus = true;
1525 frm.elements[\''.$elementName.'\'].focus();
1526 }
1527';
4f51f48f 1528
5bc97c98 1529 // Fix for bug displaying errors for elements in a group
1530 //unset($element);
1531 //$element =& $this->getElement($elementName);
1532 //end of fix
f07b9627 1533 $valFunc = 'validate_' . $this->_formName . '_' . $elementName . '(this)';
5bc97c98 1534 $onBlur = $element->getAttribute('onBlur');
1535 $onChange = $element->getAttribute('onChange');
1536 $element->updateAttributes(array('onBlur' => $onBlur . $valFunc,
1537 'onChange' => $onChange . $valFunc));
1538 }
e7004d05 1539// do not rely on frm function parameter, because htmlarea breaks it when overloading the onsubmit method
5bc97c98 1540 $js .= '
f07b9627 1541function validate_' . $this->_formName . '(frm) {
1cbb09f1 1542 if (skipClientValidation) {
1543 return true;
1544 }
5bc97c98 1545 var ret = true;
4f51f48f 1546
0befbdfd 1547 var frm = document.getElementById(\''. $this->_attributes['id'] .'\')
2ef7c374 1548 var first_focus = false;
5bc97c98 1549' . $validateJS . ';
1550 return ret;
1551}
1552//]]>
1553</script>';
1554 return $js;
1555 } // end func getValidationScript
1556 function _setDefaultRuleMessages(){
1557 foreach ($this->_rules as $field => $rulesarr){
1558 foreach ($rulesarr as $key => $rule){
1559 if ($rule['message']===null){
1560 $a=new object();
1561 $a->format=$rule['format'];
1562 $str=get_string('err_'.$rule['type'], 'form', $a);
1563 if (strpos($str, '[[')!==0){
1564 $this->_rules[$field][$key]['message']=$str;
2c412890 1565 }
5bc97c98 1566 }
1567 }
1568 }
1569 }
bb40325e 1570
d01a38cb 1571 function getLockOptionEndScript(){
dd07bbac 1572
1573 $iname = $this->getAttribute('id').'items';
f4ba7e1a 1574 $js = '<script type="text/javascript">'."\n";
5e87b920 1575 $js .= '//<![CDATA['."\n";
dd07bbac 1576 $js .= "var $iname = Array();\n";
1577
1578 foreach ($this->_dependencies as $dependentOn => $conditions){
1579 $js .= "{$iname}['$dependentOn'] = Array();\n";
1580 foreach ($conditions as $condition=>$values) {
1581 $js .= "{$iname}['$dependentOn']['$condition'] = Array();\n";
1582 foreach ($values as $value=>$dependents) {
1583 $js .= "{$iname}['$dependentOn']['$condition']['$value'] = Array();\n";
1584 $i = 0;
1585 foreach ($dependents as $dependent) {
1586 $elements = $this->_getElNamesRecursive($dependent);
46f3921e 1587 if (empty($elements)) {
1588 // probably element inside of some group
1589 $elements = array($dependent);
1590 }
dd07bbac 1591 foreach($elements as $element) {
1592 if ($element == $dependentOn) {
1593 continue;
1594 }
1595 $js .= "{$iname}['$dependentOn']['$condition']['$value'][$i]='$element';\n";
1596 $i++;
1597 }
11f260f4 1598 }
1599 }
50ef8eb9 1600 }
dd07bbac 1601 }
d01a38cb 1602 $js .="lockoptionsallsetup('".$this->getAttribute('id')."');\n";
5e87b920 1603 $js .='//]]>'."\n";
50ef8eb9 1604 $js .='</script>'."\n";
1605 return $js;
bb40325e 1606 }
d01a38cb 1607
46f3921e 1608 function _getElNamesRecursive($element) {
1609 if (is_string($element)) {
4f51f48f 1610 if (!$this->elementExists($element)) {
1611 return array();
1612 }
46f3921e 1613 $element = $this->getElement($element);
d01a38cb 1614 }
46f3921e 1615
1616 if (is_a($element, 'HTML_QuickForm_group')) {
1617 $elsInGroup = $element->getElements();
9403060a 1618 $elNames = array();
d01a38cb 1619 foreach ($elsInGroup as $elInGroup){
e850ec48 1620 if (is_a($elInGroup, 'HTML_QuickForm_group')) {
1621 // not sure if this would work - groups nested in groups
1622 $elNames = array_merge($elNames, $this->_getElNamesRecursive($elInGroup));
1623 } else {
1624 $elNames[] = $element->getElementName($elInGroup->getName());
1625 }
d01a38cb 1626 }
46f3921e 1627
1628 } else if (is_a($element, 'HTML_QuickForm_header')) {
1629 return array();
1630
1631 } else if (is_a($element, 'HTML_QuickForm_hidden')) {
1632 return array();
1633
1634 } else if (method_exists($element, 'getPrivateName')) {
1635 return array($element->getPrivateName());
1636
1637 } else {
1638 $elNames = array($element->getName());
d01a38cb 1639 }
d01a38cb 1640
46f3921e 1641 return $elNames;
50ef8eb9 1642 }
46f3921e 1643
6e372b25 1644 /**
1645 * Adds a dependency for $elementName which will be disabled if $condition is met.
9403060a 1646 * If $condition = 'notchecked' (default) then the condition is that the $dependentOn element
1647 * is not checked. If $condition = 'checked' then the condition is that the $dependentOn element
31a6c06c 1648 * is checked. If $condition is something else (like "eq" for equals) then it is checked to see if the value
1649 * of the $dependentOn element is $condition (such as equal) to $value.
6e372b25 1650 *
1651 * @param string $elementName the name of the element which will be disabled
1652 * @param string $dependentOn the name of the element whose state will be checked for
1653 * condition
1654 * @param string $condition the condition to check
19110c57 1655 * @param mixed $value used in conjunction with condition.
6e372b25 1656 */
dd07bbac 1657 function disabledIf($elementName, $dependentOn, $condition = 'notchecked', $value='1'){
1658 if (!array_key_exists($dependentOn, $this->_dependencies)) {
1659 $this->_dependencies[$dependentOn] = array();
1660 }
1661 if (!array_key_exists($condition, $this->_dependencies[$dependentOn])) {
1662 $this->_dependencies[$dependentOn][$condition] = array();
1663 }
1664 if (!array_key_exists($value, $this->_dependencies[$dependentOn][$condition])) {
1665 $this->_dependencies[$dependentOn][$condition][$value] = array();
1666 }
1667 $this->_dependencies[$dependentOn][$condition][$value][] = $elementName;
bb40325e 1668 }
dd07bbac 1669
a23f0aaf 1670 function registerNoSubmitButton($buttonname){
1671 $this->_noSubmitButtons[]=$buttonname;
1672 }
dd07bbac 1673
a23f0aaf 1674 function isNoSubmitButton($buttonname){
1675 return (array_search($buttonname, $this->_noSubmitButtons)!==FALSE);
19110c57 1676 }
dd07bbac 1677
19110c57 1678 function _registerCancelButton($addfieldsname){
1679 $this->_cancelButtons[]=$addfieldsname;
1680 }
acc9c3e0 1681 /**
1682 * Displays elements without HTML input tags.
1683 * This method is different to freeze() in that it makes sure no hidden
cc444336 1684 * elements are included in the form.
1685 * Note: If you want to make sure the submitted value is ignored, please use setDefaults().
acc9c3e0 1686 *
4af06dda 1687 * This function also removes all previously defined rules.
1688 *
acc9c3e0 1689 * @param mixed $elementList array or string of element(s) to be frozen
1690 * @since 1.0
1691 * @access public
1692 * @throws HTML_QuickForm_Error
1693 */
1694 function hardFreeze($elementList=null)
1695 {
1696 if (!isset($elementList)) {
1697 $this->_freezeAll = true;
1698 $elementList = array();
1699 } else {
1700 if (!is_array($elementList)) {
1701 $elementList = preg_split('/[ ]*,[ ]*/', $elementList);
1702 }
1703 $elementList = array_flip($elementList);
1704 }
1705
1706 foreach (array_keys($this->_elements) as $key) {
1707 $name = $this->_elements[$key]->getName();
1708 if ($this->_freezeAll || isset($elementList[$name])) {
1709 $this->_elements[$key]->freeze();
1710 $this->_elements[$key]->setPersistantFreeze(false);
1711 unset($elementList[$name]);
4af06dda 1712
1713 // remove all rules
1714 $this->_rules[$name] = array();
1715 // if field is required, remove the rule
1716 $unset = array_search($name, $this->_required);
1717 if ($unset !== false) {
1718 unset($this->_required[$unset]);
1719 }
acc9c3e0 1720 }
1721 }
1722
1723 if (!empty($elementList)) {
1724 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);
1725 }
1726 return true;
4f51f48f 1727 }
1728 /**
1729 * Hard freeze all elements in a form except those whose names are in $elementList or hidden elements in a form.
1730 *
1731 * This function also removes all previously defined rules of elements it freezes.
1732 *
1733 * @param array $elementList array or string of element(s) not to be frozen
1734 * @since 1.0
1735 * @access public
1736 * @throws HTML_QuickForm_Error
1737 */
1738 function hardFreezeAllVisibleExcept($elementList)
1739 {
1740 $elementList = array_flip($elementList);
1741 foreach (array_keys($this->_elements) as $key) {
1742 $name = $this->_elements[$key]->getName();
1743 $type = $this->_elements[$key]->getType();
56015454 1744
4f51f48f 1745 if ($type == 'hidden'){
1746 // leave hidden types as they are
1747 } elseif (!isset($elementList[$name])) {
1748 $this->_elements[$key]->freeze();
1749 $this->_elements[$key]->setPersistantFreeze(false);
1750
1751 // remove all rules
1752 $this->_rules[$name] = array();
1753 // if field is required, remove the rule
1754 $unset = array_search($name, $this->_required);
1755 if ($unset !== false) {
1756 unset($this->_required[$unset]);
1757 }
1758 }
1759 }
1760 return true;
1761 }
1762 /**
1763 * Tells whether the form was already submitted
1764 *
1765 * This is useful since the _submitFiles and _submitValues arrays
1766 * may be completely empty after the trackSubmit value is removed.
1767 *
1768 * @access public
1769 * @return bool
1770 */
1771 function isSubmitted()
1772 {
1773 return parent::isSubmitted() && (!$this->isFrozen());
1774 }
da6f8763 1775}
1776
e24b7f85 1777
da6f8763 1778/**
7f40a229 1779 * A renderer for MoodleQuickForm that only uses XHTML and CSS and no
da6f8763 1780 * table tags, extends PEAR class HTML_QuickForm_Renderer_Tableless
7f40a229 1781 *
da6f8763 1782 * Stylesheet is part of standard theme and should be automatically included.
1783 *
1784 * @author Jamie Pratt <me@jamiep.org>
1785 * @license gpl license
1786 */
7f40a229 1787class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
da6f8763 1788
1789 /**
1790 * Element template array
1791 * @var array
1792 * @access private
1793 */
1794 var $_elementTemplates;
49c53687 1795 /**
1796 * Template used when opening a hidden fieldset
1797 * (i.e. a fieldset that is opened when there is no header element)
1798 * @var string
1799 * @access private
1800 */
c02345e3 1801 var $_openHiddenFieldsetTemplate = "\n\t<fieldset class=\"hidden\"><div>";
19194f82 1802 /**
1803 * Header Template string
1804 * @var string
1805 * @access private
1806 */
1807 var $_headerTemplate =
c02345e3 1808 "\n\t\t<legend class=\"ftoggler\">{header}</legend>\n\t\t<div class=\"advancedbutton\">{advancedimg}{button}</div><div class=\"fcontainer clearfix\">\n\t\t";
7f40a229 1809
49c53687 1810 /**
bc9ec4a6 1811 * Template used when opening a fieldset
1812 * @var string
1813 * @access private
1814 */
1815 var $_openFieldsetTemplate = "\n\t<fieldset class=\"clearfix\" {id}>";
1816
1817 /**
49c53687 1818 * Template used when closing a fieldset
1819 * @var string
1820 * @access private
1821 */
c02345e3 1822 var $_closeFieldsetTemplate = "\n\t\t</div></fieldset>";
42f248e6 1823
49c53687 1824 /**
1825 * Required Note template string
1826 * @var string
1827 * @access private
1828 */
7f40a229 1829 var $_requiredNoteTemplate =
6ba2c73d 1830 "\n\t\t<div class=\"fdescription required\">{requiredNote}</div>";
7f40a229 1831
19194f82 1832 var $_advancedElements = array();
1833
1834 /**
1835 * Whether to display advanced elements (on page load)
1836 *
1837 * @var integer 1 means show 0 means hide
1838 */
1839 var $_showAdvanced;
1840
7f40a229 1841 function MoodleQuickForm_Renderer(){
42f248e6 1842 // switch next two lines for ol li containers for form items.
49c7f3a8 1843 // $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>');
9aa022fe 1844 $this->_elementTemplates = array(
f8b9ac74 1845 'default'=>"\n\t\t".'<div class="fitem {advanced}<!-- BEGIN required --> required<!-- END required -->"><div class="fitemtitle"><label>{label}<!-- BEGIN required -->{req}<!-- END required -->{advancedimg} {help}</label></div><div class="felement {type}<!-- BEGIN error --> error<!-- END error -->"><!-- BEGIN error --><span class="error">{error}</span><br /><!-- END error -->{element}</div></div>',
49c7f3a8 1846
f9f9be73 1847 'fieldset'=>"\n\t\t".'<div class="fitem {advanced}<!-- BEGIN required --> required<!-- END required -->"><div class="fitemtitle"><div class="fgrouplabel"><label>{label}<!-- BEGIN required -->{req}<!-- END required -->{advancedimg} {help}</label></div></div><fieldset class="felement {type}<!-- BEGIN error --> error<!-- END error -->"><!-- BEGIN error --><span class="error">{error}</span><br /><!-- END error -->{element}</fieldset></div>',
4f51f48f 1848
f9f9be73 1849 '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>',
43914931 1850
1ae1941e 1851'warning'=>"\n\t\t".'<div class="fitem {advanced}">{element}</div>',
1852
4f51f48f 1853 'nodisplay'=>'');
da6f8763 1854
1855 parent::HTML_QuickForm_Renderer_Tableless();
1856 }
7f40a229 1857
19194f82 1858 function setAdvancedElements($elements){
1859 $this->_advancedElements = $elements;
1860 }
1861
1862 /**
1863 * What to do when starting the form
1864 *
1865 * @param MoodleQuickForm $form
1866 */
da6f8763 1867 function startForm(&$form){
9403060a 1868 $this->_reqHTML = $form->getReqHTML();
1869 $this->_elementTemplates = str_replace('{req}', $this->_reqHTML, $this->_elementTemplates);
19194f82 1870 $this->_advancedHTML = $form->getAdvancedHTML();
1871 $this->_showAdvanced = $form->getShowAdvanced();
da6f8763 1872 parent::startForm($form);
4f51f48f 1873 if ($form->isFrozen()){
1874 $this->_formTemplate = "\n<div class=\"mform frozen\">\n{content}\n</div>";
1875 } else {
1876 $this->_hiddenHtml .= $form->_pageparams;
1877 }
1878
1879
da6f8763 1880 }
7f40a229 1881
da6f8763 1882 function startGroup(&$group, $required, $error){
1883 if (method_exists($group, 'getElementTemplateType')){
e249661f 1884 $html = $this->_elementTemplates[$group->getElementTemplateType()];
da6f8763 1885 }else{
1886 $html = $this->_elementTemplates['default'];
7f40a229 1887
da6f8763 1888 }
19194f82 1889 if ($this->_showAdvanced){
1890 $advclass = ' advanced';
1891 } else {
1892 $advclass = ' advanced hide';
1893 }
1894 if (isset($this->_advancedElements[$group->getName()])){
1895 $html =str_replace(' {advanced}', $advclass, $html);
1896 $html =str_replace('{advancedimg}', $this->_advancedHTML, $html);
1897 } else {
1898 $html =str_replace(' {advanced}', '', $html);
1899 $html =str_replace('{advancedimg}', '', $html);
1900 }
da6f8763 1901 if (method_exists($group, 'getHelpButton')){
1902 $html =str_replace('{help}', $group->getHelpButton(), $html);
1903 }else{
1904 $html =str_replace('{help}', '', $html);
da6f8763 1905 }
e7004d05 1906 $html =str_replace('{name}', $group->getName(), $html);
49c53687 1907 $html =str_replace('{type}', 'fgroup', $html);
7f40a229 1908
da6f8763 1909 $this->_templates[$group->getName()]=$html;
1910 // Fix for bug in tableless quickforms that didn't allow you to stop a
1911 // fieldset before a group of elements.
1912 // if the element name indicates the end of a fieldset, close the fieldset
1913 if ( in_array($group->getName(), $this->_stopFieldsetElements)
1914 && $this->_fieldsetsOpen > 0
1915 ) {
1916 $this->_html .= $this->_closeFieldsetTemplate;
1917 $this->_fieldsetsOpen--;
1918 }
1919 parent::startGroup($group, $required, $error);
1920 }
7f40a229 1921
da6f8763 1922 function renderElement(&$element, $required, $error){
172dd12c 1923 //manipulate id of all elements before rendering
86aab05c 1924 if (!is_null($element->getAttribute('id'))) {
1925 $id = $element->getAttribute('id');
1926 } else {
1927 $id = $element->getName();
1928 }
1929 //strip qf_ prefix and replace '[' with '_' and strip ']'
1930 $id = preg_replace(array('/^qf_|\]/', '/\[/'), array('', '_'), $id);
1931 if (strpos($id, 'id_') !== 0){
1932 $element->updateAttributes(array('id'=>'id_'.$id));
1933 }
1934
1935 //adding stuff to place holders in template
172dd12c 1936 //check if this is a group element first
906ebc4b 1937 if (($this->_inGroup) and !empty($this->_groupElementTemplate)) {
3493eb15 1938 // so it gets substitutions for *each* element
1ae1941e 1939 $html = $this->_groupTemplates[$element->getName()];
906ebc4b 1940 }
1941 elseif (method_exists($element, 'getElementTemplateType')){
da6f8763 1942 $html = $this->_elementTemplates[$element->getElementTemplateType()];
1943 }else{
1944 $html = $this->_elementTemplates['default'];
19194f82 1945 }
1946 if ($this->_showAdvanced){
1947 $advclass = ' advanced';
1948 } else {
1949 $advclass = ' advanced hide';
1950 }
1951 if (isset($this->_advancedElements[$element->getName()])){
1952 $html =str_replace(' {advanced}', $advclass, $html);
1953 } else {
1954 $html =str_replace(' {advanced}', '', $html);
1955 }
1956 if (isset($this->_advancedElements[$element->getName()])||$element->getName() == 'mform_showadvanced'){
1957 $html =str_replace('{advancedimg}', $this->_advancedHTML, $html);
1958 } else {
1959 $html =str_replace('{advancedimg}', '', $html);
da6f8763 1960 }
49c53687 1961 $html =str_replace('{type}', 'f'.$element->getType(), $html);
e7004d05 1962 $html =str_replace('{name}', $element->getName(), $html);
da6f8763 1963 if (method_exists($element, 'getHelpButton')){
9403060a 1964 $html = str_replace('{help}', $element->getHelpButton(), $html);
da6f8763 1965 }else{
9403060a 1966 $html = str_replace('{help}', '', $html);
7f40a229 1967
da6f8763 1968 }
906ebc4b 1969 if (($this->_inGroup) and !empty($this->_groupElementTemplate)) {
1970 $this->_groupElementTemplate = $html;
41b6d001 1971 }
906ebc4b 1972 elseif (!isset($this->_templates[$element->getName()])) {
1973 $this->_templates[$element->getName()] = $html;
172dd12c 1974 }
1975
da6f8763 1976 parent::renderElement($element, $required, $error);
1977 }
19194f82 1978
bb40325e 1979 function finishForm(&$form){
4f51f48f 1980 if ($form->isFrozen()){
1981 $this->_hiddenHtml = '';
1982 }
bb40325e 1983 parent::finishForm($form);
4f51f48f 1984 if ((!$form->isFrozen()) && ('' != ($script = $form->getLockOptionEndScript()))) {
1985 // add a lockoptions script
bb40325e 1986 $this->_html = $this->_html . "\n" . $script;
1987 }
1988 }
19194f82 1989 /**
1990 * Called when visiting a header element
1991 *
1992 * @param object An HTML_QuickForm_header element being visited
1993 * @access public
1994 * @return void
1995 */
1996 function renderHeader(&$header) {
1997 $name = $header->getName();
1998
1999 $id = empty($name) ? '' : ' id="' . $name . '"';
78354cec 2000 $id = preg_replace(array('/\]/', '/\[/'), array('', '_'), $id);
19194f82 2001 if (is_null($header->_text)) {
2002 $header_html = '';
2003 } elseif (!empty($name) && isset($this->_templates[$name])) {
2004 $header_html = str_replace('{header}', $header->toHtml(), $this->_templates[$name]);
2005 } else {
2006 $header_html = str_replace('{header}', $header->toHtml(), $this->_headerTemplate);
2007 }
2008
2009 if (isset($this->_advancedElements[$name])){
2010 $header_html =str_replace('{advancedimg}', $this->_advancedHTML, $header_html);
2011 } else {
2012 $header_html =str_replace('{advancedimg}', '', $header_html);
2013 }
2014 $elementName='mform_showadvanced';
2015 if ($this->_showAdvanced==0){
2016 $buttonlabel = get_string('showadvanced', 'form');
2017 } else {
2018 $buttonlabel = get_string('hideadvanced', 'form');
2019 }
2020
2021 if (isset($this->_advancedElements[$name])){
cd350b53 2022 require_js(array('yui_yahoo', 'yui_event'));
7c9b1d31 2023 // this is tricky - the first submit button on form is "clicked" if user presses enter
2024 // we do not want to "submit" using advanced button if javascript active
7c9b1d31 2025 $button_nojs = '<input name="'.$elementName.'" value="'.$buttonlabel.'" type="submit" />';
cd350b53 2026
2027 $buttonlabel = addslashes_js($buttonlabel);
2028 $showtext = addslashes_js(get_string('showadvanced', 'form'));
2029 $hidetext = addslashes_js(get_string('hideadvanced', 'form'));
2030 $button = '<script id="' . $name . '_script" type="text/javascript">' . "
2031showAdvancedInit('{$name}_script', '$elementName', '$buttonlabel', '$hidetext', '$showtext');
2032" . '</script><noscript><div style="display:inline">'.$button_nojs.'</div></noscript>'; // the extra div should fix xhtml validation
172dd12c 2033
7c9b1d31 2034 $header_html = str_replace('{button}', $button, $header_html);
19194f82 2035 } else {
7c9b1d31 2036 $header_html = str_replace('{button}', '', $header_html);
19194f82 2037 }
2038
2039 if ($this->_fieldsetsOpen > 0) {
2040 $this->_html .= $this->_closeFieldsetTemplate;
2041 $this->_fieldsetsOpen--;
2042 }
2043
2044 $openFieldsetTemplate = str_replace('{id}', $id, $this->_openFieldsetTemplate);
2045 if ($this->_showAdvanced){
2046 $advclass = ' class="advanced"';
2047 } else {
2048 $advclass = ' class="advanced hide"';
2049 }
2050 if (isset($this->_advancedElements[$name])){
2051 $openFieldsetTemplate = str_replace('{advancedclass}', $advclass, $openFieldsetTemplate);
2052 } else {
2053 $openFieldsetTemplate = str_replace('{advancedclass}', '', $openFieldsetTemplate);
2054 }
2055 $this->_html .= $openFieldsetTemplate . $header_html;
2056 $this->_fieldsetsOpen++;
2057 } // end func renderHeader
2058
2059 function getStopFieldsetElements(){
2060 return $this->_stopFieldsetElements;
2061 }
da6f8763 2062}
2063
da6f8763 2064
9403060a 2065$GLOBALS['_HTML_QuickForm_default_renderer'] =& new MoodleQuickForm_Renderer();
da6f8763 2066
c583482c 2067// Please keep this list in alphabetical order.
2068MoodleQuickForm::registerElementType('advcheckbox', "$CFG->libdir/form/advcheckbox.php", 'MoodleQuickForm_advcheckbox');
2069MoodleQuickForm::registerElementType('button', "$CFG->libdir/form/button.php", 'MoodleQuickForm_button');
2070MoodleQuickForm::registerElementType('cancel', "$CFG->libdir/form/cancel.php", 'MoodleQuickForm_cancel');
7f40a229 2071MoodleQuickForm::registerElementType('checkbox', "$CFG->libdir/form/checkbox.php", 'MoodleQuickForm_checkbox');
c583482c 2072MoodleQuickForm::registerElementType('choosecoursefile', "$CFG->libdir/form/choosecoursefile.php", 'MoodleQuickForm_choosecoursefile');
2073MoodleQuickForm::registerElementType('choosecoursefileorimsrepo', "$CFG->libdir/form/choosecoursefileorimsrepo.php", 'MoodleQuickForm_choosecoursefileorimsrepo');
2074MoodleQuickForm::registerElementType('date_selector', "$CFG->libdir/form/dateselector.php", 'MoodleQuickForm_date_selector');
2075MoodleQuickForm::registerElementType('date_time_selector', "$CFG->libdir/form/datetimeselector.php", 'MoodleQuickForm_date_time_selector');
32fa2272 2076MoodleQuickForm::registerElementType('duration', "$CFG->libdir/form/duration.php", 'MoodleQuickForm_duration');
c583482c 2077MoodleQuickForm::registerElementType('editor', "$CFG->libdir/form/editor.php", 'MoodleQuickForm_editor');
7f40a229 2078MoodleQuickForm::registerElementType('file', "$CFG->libdir/form/file.php", 'MoodleQuickForm_file');
241431cd 2079MoodleQuickForm::registerElementType('filemanager', "$CFG->libdir/form/filemanager.php", 'MoodleQuickForm_filemanager');
c5704ec6 2080MoodleQuickForm::registerElementType('filepicker', "$CFG->libdir/form/filepicker.php", 'MoodleQuickForm_filepicker');
c583482c 2081MoodleQuickForm::registerElementType('format', "$CFG->libdir/form/format.php", 'MoodleQuickForm_format');
7f40a229 2082MoodleQuickForm::registerElementType('group', "$CFG->libdir/form/group.php", 'MoodleQuickForm_group');
c583482c 2083MoodleQuickForm::registerElementType('header', "$CFG->libdir/form/header.php", 'MoodleQuickForm_header');
2084MoodleQuickForm::registerElementType('hidden', "$CFG->libdir/form/hidden.php", 'MoodleQuickForm_hidden');
2085MoodleQuickForm::registerElementType('htmleditor', "$CFG->libdir/form/htmleditor.php", 'MoodleQuickForm_htmleditor');
2086MoodleQuickForm::registerElementType('modgrade', "$CFG->libdir/form/modgrade.php", 'MoodleQuickForm_modgrade');
2087MoodleQuickForm::registerElementType('modvisible', "$CFG->libdir/form/modvisible.php", 'MoodleQuickForm_modvisible');
7f40a229 2088MoodleQuickForm::registerElementType('password', "$CFG->libdir/form/password.php", 'MoodleQuickForm_password');
4f51f48f 2089MoodleQuickForm::registerElementType('passwordunmask', "$CFG->libdir/form/passwordunmask.php", 'MoodleQuickForm_passwordunmask');
c583482c 2090MoodleQuickForm::registerElementType('questioncategory', "$CFG->libdir/form/questioncategory.php", 'MoodleQuickForm_questioncategory');
7f40a229 2091MoodleQuickForm::registerElementType('radio', "$CFG->libdir/form/radio.php", 'MoodleQuickForm_radio');
c583482c 2092MoodleQuickForm::registerElementType('recaptcha', "$CFG->libdir/form/recaptcha.php", 'MoodleQuickForm_recaptcha');
7f40a229 2093MoodleQuickForm::registerElementType('select', "$CFG->libdir/form/select.php", 'MoodleQuickForm_select');
4f51f48f 2094MoodleQuickForm::registerElementType('selectgroups', "$CFG->libdir/form/selectgroups.php", 'MoodleQuickForm_selectgroups');
c583482c 2095MoodleQuickForm::registerElementType('selectwithlink', "$CFG->libdir/form/selectwithlink.php", 'MoodleQuickForm_selectwithlink');
2096MoodleQuickForm::registerElementType('selectyesno', "$CFG->libdir/form/selectyesno.php", 'MoodleQuickForm_selectyesno');
2097MoodleQuickForm::registerElementType('static', "$CFG->libdir/form/static.php", 'MoodleQuickForm_static');
2098MoodleQuickForm::registerElementType('submit', "$CFG->libdir/form/submit.php", 'MoodleQuickForm_submit');
6073a598 2099MoodleQuickForm::registerElementType('submitlink', "$CFG->libdir/form/submitlink.php", 'MoodleQuickForm_submitlink');
9f1c9dfc 2100MoodleQuickForm::registerElementType('tags', "$CFG->libdir/form/tags.php", 'MoodleQuickForm_tags');
7f40a229 2101MoodleQuickForm::registerElementType('text', "$CFG->libdir/form/text.php", 'MoodleQuickForm_text');
2102MoodleQuickForm::registerElementType('textarea', "$CFG->libdir/form/textarea.php", 'MoodleQuickForm_textarea');
1ae1941e 2103MoodleQuickForm::registerElementType('warning', "$CFG->libdir/form/warning.php", 'MoodleQuickForm_warning');
49c7f3a8 2104?>