libdir.'/pear' )){ ini_set('include_path', $CFG->libdir.'/pear' . PATH_SEPARATOR . ini_get('include_path')); } require_once 'HTML/QuickForm.php'; require_once 'HTML/QuickForm/DHTMLRulesTableless.php'; require_once 'HTML/QuickForm/Renderer/Tableless.php'; require_once $CFG->libdir.'/uploadlib.php'; if ($CFG->debug >= DEBUG_ALL){ PEAR::setErrorHandling(PEAR_ERROR_PRINT); } /** * Moodle specific wrapper that separates quickforms syntax from moodle code. You won't directly * use this class you should write a class defintion which extends this class or a more specific * subclass such a moodleform_mod for each form you want to display and/or process with formslib. * * You will write your own definition() method which performs the form set up. */ class moodleform { var $_formname; // form name /** * quickform object definition * * @var MoodleQuickForm */ var $_form; /** * globals workaround * * @var array */ var $_customdata; /** * file upload manager * * @var upload_manager */ var $_upload_manager; // /** * The constructor function calls the abstract function definition() and it will then * process and clean and attempt to validate incoming data. * * It will call your custom validate method to validate data and will also check any rules * you have specified in definition using addRule * * The name of the form (id attribute of the form) is automatically generated depending on * the name you gave the class extending moodleform. You should call your class something * like * * @param string $action the action attribute for the form. * @param array $customdata if your form defintion method needs access to data such as $course * $cm, etc. to construct the form definition then pass it in this array. You can * use globals for somethings. * @param string $method if you set this to anything other than 'post' then _GET and _POST will * be merged and used as incoming data to the form. * @param string $target target frame for form submission. You will rarely use this. Don't use * it if you don't need to as the target attribute is deprecated in xhtml * strict. * @param mixed $attributes you can pass a string of html attributes here or an array. * @return moodleform */ function moodleform($action, $customdata=null, $method='post', $target='', $attributes=null) { $this->_formname = rtrim(get_class($this), '_form'); $this->_customdata = $customdata; $this->_form =& new MoodleQuickForm($this->_formname, $method, $action, $target, $attributes); $this->definition(); $this->_form->addElement('hidden', 'sesskey', null); // automatic sesskey protection $this->_form->setDefault('sesskey', sesskey()); $this->_form->addElement('hidden', '_qf__'.$this->_formname, null); // form submission marker $this->_form->setDefault('_qf__'.$this->_formname, 1); $this->_form->_setDefaultRuleMessages(); // we have to know all input types before processing submission ;-) $this->_process_submission($method); // update form definition based on final data $this->definition_after_data(); } /** * To autofocus on first form element or first element with error. * * @return string javascript to select form element with first error or * first element if no errors. Use this as a parameter * when calling print_header */ function focus(){ $form =& $this->_form; $elkeys=array_keys($form->_elementIndex); if (isset($form->_errors) && 0 != count($form->_errors)){ $errorkeys = array_keys($form->_errors); $elkeys = array_intersect($elkeys, $errorkeys); } $names=null; while (!$names){ $el = array_shift($elkeys); $names=$form->_getElNamesRecursive($el); } $name=array_shift($names); $focus='forms[\''.$this->_form->getAttribute('id').'\'].elements[\''.$name.'\']'; return $focus; } /** * Internal method. Alters submitted data to be suitable for quickforms processing. * Must be called when the form is fully set up. */ function _process_submission($method) { $submission = array(); if ($method == 'post') { if (!empty($_POST)) { $submission = $_POST; } } else { $submission = array_merge_recursive($_GET, $_POST); // emulate handling of parameters in xxxx_param() } // following trick is needed to enable proper sesskey checks when using GET forms // the _qf__.$this->_formname serves as a marker that form was actually submitted if (array_key_exists('_qf__'.$this->_formname, $submission) and $submission['_qf__'.$this->_formname] == 1) { if (!confirm_sesskey()) { error('Incorrect sesskey submitted, form not accepted!'); } $files = $_FILES; } else { $submission = array(); $files = array(); } $this->_form->updateSubmission($submission, $files); } /** * Internal method. Validates all uploaded files. */ function _validate_files() { if (empty($_FILES)) { // we do not need to do any checks because no files were submitted // TODO: find out why server side required rule does not work for uploaded files; // testing is easily done by always returning true from this function and adding // $mform->addRule('soubor', get_string('required'), 'required', null, 'server'); // and submitting form without selected file return true; } $errors = array(); $mform =& $this->_form; // create default upload manager if not already created if (empty($this->_upload_manager)) { $this->_upload_manager = new upload_manager(); } // check the files $status = $this->_upload_manager->preprocess_files(); // now check that we really want each file foreach ($_FILES as $elname=>$file) { if ($mform->elementExists($elname) and $mform->getElementType($elname)=='file') { $required = $mform->isElementRequired($elname); if (!empty($this->_upload_manager->files[$elname]['uploadlog']) and empty($this->_upload_manager->files[$elname]['clear'])) { if (!$required and $file['error'] == UPLOAD_ERR_NO_FILE) { // file not uploaded and not required - ignore it continue; } $errors[$elname] = $this->_upload_manager->files[$elname]['uploadlog']; } } else { error('Incorrect upload attemp!'); } } // return errors if found if ($status and 0 == count($errors)){ return true; } else { return $errors; } } /** * Load in existing data as form defaults. Usually new entry defaults are stored directly in * form definition (new entry form); this function is used to load in data where values * already exist and data is being edited (edit entry form). * * @param mixed $default_values object or array of default values * @param bool $slased true if magic quotes applied to data values */ function set_defaults($default_values, $slashed=false) { if (is_object($default_values)) { $default_values = (array)$default_values; } $filter = $slashed ? 'stripslashes' : NULL; $this->_form->setDefaults($default_values, $filter); //update form definition when data changed $this->definition_after_data(); } /** * Set maximum allowed uploaded file size. * Must be used BEFORE creating of file element! * * @param object $course * @param object $modbytes - max size limit defined in module */ function set_max_file_size($course=null, $modbytes=0) { global $CFG, $COURSE; if (empty($course->id)) { $course = $COURSE; } $maxbytes = get_max_upload_file_size($CFG->maxbytes, $course->maxbytes, $modbytes); $this->_form->setMaxFileSize($maxbytes); } /** * Check that form was submitted. Does not check validity of submitted data. * * @return bool true if form properly submitted */ function is_submitted() { return $this->_form->isSubmitted(); } /** * Check that form data is valid. * * @return bool true if form data valid */ function is_validated() { static $validated = null; // one validation is enough $mform =& $this->_form; foreach ($mform->_noSubmitButtons as $nosubmitbutton){ if (optional_param($nosubmitbutton, 0, PARAM_RAW)){ return false; } } if ($validated === null) { $internal_val = $mform->validate(); $moodle_val = $this->validation($mform->exportValues(null, true)); if ($moodle_val !== true) { if (!empty($moodle_val)) { foreach ($moodle_val as $element=>$msg) { $mform->setElementError($element, $msg); } } $moodle_val = false; } $file_val = $this->_validate_files(); if ($file_val !== true) { if (!empty($file_val)) { foreach ($file_val as $element=>$msg) { $mform->setElementError($element, $msg); } } $file_val = false; } $validated = ($internal_val and $moodle_val and $file_val); } return $validated; } /** * Return true if a cancel button has been pressed resulting in the form being submitted. * * @return boolean true if a cancel button has been pressed */ function is_cancelled(){ $mform =& $this->_form; foreach ($mform->_cancelButtons as $cancelbutton){ if (optional_param($cancelbutton, 0, PARAM_RAW)){ return true; } } return false; } /** * Return submitted data if properly submitted or returns NULL if validation fails or * if there is no submitted data. * * @param bool $slashed true means return data with addslashes applied * @return object submitted data; NULL if not valid or not submitted */ function data_submitted($slashed=true) { $mform =& $this->_form; if ($this->is_submitted() and $this->is_validated()) { $data = $mform->exportValues(null, $slashed); unset($data['sesskey']); // we do not need to return sesskey unset($data['_qf__'.$this->_formname]); // we do not need the submission marker too if (empty($data)) { return NULL; } else { return (object)$data; } } else { return NULL; } } /** * Save verified uploaded files into directory. Upload process can be customised from definition() * method by creating instance of upload manager and storing it in $this->_upload_form * * @param string $destination where to store uploaded files * @return bool success */ function save_files($destination) { if (empty($this->_upload_manager)) { return false; } if ($this->is_submitted() and $this->is_validated()) { return $this->_upload_manager->save_files($destination); } return false; } /** * Print html form. */ function display() { $this->_form->display(); } /** * Abstract method - always override! * * If you need special handling of uploaded files, create instance of $this->_upload_manager here. */ function definition() { error('Abstract form_definition() method in class '.get_class($this).' must be overriden, please fix the code.'); } /** * Dummy stub method - override if you need to setup the form depending on current * values. This method is called after definition(), data submission and set_defaults(). * All form setup that is dependent on form values should go in here. */ function definition_after_data(){ } /** * Dummy stub method - override if you needed to perform some extra validation. * If there are errors return array of errors ("fieldname"=>"error message"), * otherwise true if ok. * * @param array $data array of ("fieldname"=>value) of submitted data * @return bool array of errors or true if ok */ function validation($data) { return true; } /** * Method to add a repeating group of elements to a form. * * @param array $elementobjs Array of elements or groups of elements that are to be repeated * @param integer $repeats no of times to repeat elements initially * @param array $options Array of options to apply to elements. Array keys are element names. * This is an array of arrays. The second sets of keys are the option types * for the elements : * 'default' - default value is value * 'type' - PARAM_* constant is value * 'helpbutton' - helpbutton params array is value * 'disabledif' - last three moodleform::disabledIf() * params are value as an array * @param string $repeathiddenname name for hidden element storing no of repeats in this form * @param string $addfieldsname name for button to add more fields * @param int $addfieldsno how many fields to add at a time * @param array $addstring array of params for get_string for name of button, $a is no of fields that * will be added. */ function repeat_elements($elementobjs, $repeats, $options, $repeathiddenname, $addfieldsname, $addfieldsno=5, $addstring=array('addfields', 'form')){ $repeats = optional_param($repeathiddenname, $repeats, PARAM_INT); $addfields = optional_param($addfieldsname, '', PARAM_TEXT); if (!empty($addfields)){ $repeats += $addfieldsno; } $mform =& $this->_form; $mform->_registerNoSubmitButton($addfieldsname); $mform->addElement('hidden', $repeathiddenname, $repeats); //value not to be overridden by submitted value $mform->setConstants(array($repeathiddenname=>$repeats)); for ($i=0; $i<$repeats; $i++) { foreach ($elementobjs as $elementobj){ $elementclone=clone($elementobj); $name=$elementclone->getName(); $elementclone->setName($name."[$i]"); if (is_a($elementclone, 'HTML_QuickForm_header')){ $value=$elementclone->_text; $elementclone->setValue($value.' '.($i+1)); } $mform->addElement($elementclone); } } for ($i=0; $i<$repeats; $i++) { foreach ($options as $elementname => $elementoptions){ $pos=strpos($elementname, '['); if ($pos!==FALSE){ $realelementname = substr($elementname, 0, $pos+1)."[$i]"; $realelementname .= substr($elementname, $pos+1); }else { $realelementname = $elementname."[$i]"; } foreach ($elementoptions as $option => $params){ switch ($option){ case 'default' : $mform->setDefault($realelementname, $params); break; case 'type' : $mform->setType($realelementname, $params); break; case 'helpbutton' : $mform->setHelpButton($realelementname, $params); break; case 'disabledif' : $mform->disabledIf($realelementname, $params[0], $params[1], $params[2]); break; } } } } $mform->addElement('submit', $addfieldsname, get_string('addfields', 'form', $addfieldsno), array('onclick'=>'this.form.submit();'));//need this to bypass client validation $renderer =& $mform->defaultRenderer(); $renderer->addStopFieldsetElements($addfieldsname); } } /** * You never extend this class directly. The class methods of this class are available from * the private $this->_form property on moodleform and it's children. You generally only * call methods on this class from within abstract methods that you override on moodleform such * as definition and definition_after_data * */ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless { var $_types = array(); var $_dependencies = array(); /** * Array of buttons that if pressed do not result in the processing of the form. * * @var array */ var $_noSubmitButtons=array(); /** * Array of buttons that if pressed do not result in the processing of the form. * * @var array */ var $_cancelButtons=array(); /** * Class constructor - same parameters as HTML_QuickForm_DHTMLRulesTableless * @param string $formName Form's name. * @param string $method (optional)Form's method defaults to 'POST' * @param string $action (optional)Form's action * @param string $target (optional)Form's target defaults to none * @param mixed $attributes (optional)Extra attributes for