. /** * @package qtype_algebra * @copyright Roger Moore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once($CFG->dirroot . '/question/type/edit_question_form.php'); require_once($CFG->dirroot . '/question/type/algebra/questiontype.php'); require_once($CFG->dirroot . '/question/type/algebra/parser.php'); // Override the default number of answers and the number to add to avoid clutter. // Algebra questions will likely not have huge number of different answers. define("SYMB_QUESTION_NUMANS_START", 2); define("SYMB_QUESTION_NUMANS_ADD", 1); // Override the default number of variables and the number to add to avoid clutter. // Algebra questions will likely not have huge number of different variables. define("SYMB_QUESTION_NUMVAR_START", 2); define("SYMB_QUESTION_NUMVAR_ADD", 1); /** * symoblic editing form definition. */ class qtype_algebra_edit_form extends question_edit_form { /** * Add question-type specific form fields. * * @param MoodleQuickForm $mform the form being built. */ protected function definition_inner($mform) { global $CFG; // Algebra questions options. $mform->addElement('header', 'algebraoptions', get_string('algebraoptions', 'qtype_algebra')); // Add the select control which will select the comparison type to use. $mform->addElement('select', 'compareby', get_string('compareby', 'qtype_algebra'), array( "sage" => get_string('comparesage', 'qtype_algebra'), "eval" => get_string('compareeval', 'qtype_algebra'), "equiv" => get_string('compareequiv', 'qtype_algebra') )); $mform->addHelpButton('compareby', 'compareby', 'qtype_algebra'); $mform->setDefault('compareby', $CFG->qtype_algebra_method); // Add the control to select the number of checks to perform. // First create an array with all the allowed values. We will then use this array // with the array_combine function to create a single array where the keys are the // same as the array values. $chk_array=array( '1', '2', '3', '5', '7', '10', '20', '30', '50', '70', '100', '200', '300', '500', '700', '1000'); // Add the select element using the array_combine method discussed above. $mform->addElement('select', 'nchecks', get_string('nchecks', 'qtype_algebra'), array_combine($chk_array, $chk_array)); $mform->addHelpButton('nchecks', 'nchecks', 'qtype_algebra'); // Set the default number of checks to perform. $mform->setDefault('nchecks', '10'); // Add the box to set the tolerance to use when performing evaluation checks. $mform->addElement('text', 'tolerance', get_string('tolerance', 'qtype_algebra')); $mform->addHelpButton('tolerance', 'tolerance', 'qtype_algebra'); $mform->setType('tolerance', PARAM_NUMBER); $mform->setDefault('tolerance', '0.001'); // Add an entry for the answer box prefix. $mform->addElement('text', 'answerprefix', get_string('answerprefix', 'qtype_algebra'), array('size'=>55)); $mform->addHelpButton('answerprefix', 'answerprefix', 'qtype_algebra'); $mform->setType('answerprefix', PARAM_RAW); // Add an entry for a disallowed expression. $mform->addElement('text', 'disallow', get_string('disallow', 'qtype_algebra'), array('size'=>55)); $mform->addHelpButton('disallow', 'disallow', 'qtype_algebra'); $mform->setType('disallow', PARAM_RAW); // Create an array which will store the function checkboxes. $func_group=array(); // Create an array to add spacers between the boxes. $spacers=array('
'); // Add the initial all functions box to the list of check boxes. $func_group[] =& $mform->createElement('checkbox', 'all', '', get_string('allfunctions', 'qtype_algebra')); // Create a checkbox element for each function understood by the parser. for ($i=0; $icreateElement('checkbox', $func, '', $func); if (($i % 6) == 5) { $spacers[]='
'; } else { $spacers[]=str_repeat(' ', 8-strlen($func)); } } // Create and add the group of function controls to the form. $mform->addGroup($func_group, 'allowedfuncs', get_string('allowedfuncs', 'qtype_algebra'), $spacers, true); $mform->addHelpButton('allowedfuncs', 'allowedfuncs', 'qtype_algebra'); $mform->disabledif ('allowedfuncs', 'allowedfuncs[all]', 'checked'); $mform->setDefault('allowedfuncs[all]', 'checked'); $mform->addElement('static', 'variablesinstruct', get_string('variables', 'qtype_algebra'), get_string('filloutonevariable', 'qtype_algebra')); $mform->closeHeaderBefore('variablesinstruct'); // Create the array for the list of variables used in the question. $repeated=array(); // Create the array for the list of repeated options used by the variable subforms. $repeatedoptions = array(); // Add the form elements to enter the variables. $repeated[] =& $mform->createElement('header', 'variablehdr', get_string('variableno', 'qtype_algebra', '{no}')); // $repeatedoptions['variablehdr']['helpbutton'] = array('variable', get_string('variable', 'qtype_algebra'), // 'qtype_algebra'); $repeated[] =& $mform->createElement('text', 'variable', get_string('variablename', 'qtype_algebra'), array('size'=>20)); $mform->setType('variable', PARAM_RAW); $repeated[] =& $mform->createElement('text', 'varmin', get_string('varmin', 'qtype_algebra'), array('size'=>20)); $mform->setType('varmin', PARAM_RAW); $repeatedoptions['varmin']['default'] = ''; $repeated[] =& $mform->createElement('text', 'varmax', get_string('varmax', 'qtype_algebra'), array('size'=>20)); $mform->setType('varmax', PARAM_RAW); $repeatedoptions['varmax']['default'] = ''; // Get the current number of variables defined, if any. if (isset($this->question->options)) { $countvars = count($this->question->options->variables); } else { $countvars = 0; } // Come up with the number of variable entries to add to the form at the start. if ($this->question->formoptions->repeatelements) { $repeatsatstart = (SYMB_QUESTION_NUMVAR_START > ($countvars + SYMB_QUESTION_NUMVAR_ADD))? SYMB_QUESTION_NUMVAR_START : ($countvars + SYMB_QUESTION_NUMVAR_ADD); } else { $repeatsatstart = $countvars; } $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 'novariables', 'addvariables', SYMB_QUESTION_NUMVAR_ADD, get_string('addmorevariableblanks', 'qtype_algebra')); $mform->addElement('static', 'answersinstruct', get_string('correctanswers', 'qtype_algebra'), get_string('filloutoneanswer', 'qtype_algebra')); $mform->closeHeaderBefore('answersinstruct'); $this->add_per_answer_fields($mform, get_string('answerno', 'qtype_algebra', '{no}'), question_bank::fraction_options(), SYMB_QUESTION_NUMANS_START, SYMB_QUESTION_NUMANS_ADD); $this->add_interactive_settings(); } protected function data_preprocessing($question) { $question = parent::data_preprocessing($question); $question = $this->data_preprocessing_answers($question); $question = $this->data_preprocessing_hints($question); if (!empty($question->options)) { $question->compareby = $question->options->compareby; $question->nchecks = $question->options->nchecks; $question->tolerance = $question->options->tolerance; $question->allowedfuncs = $question->options->allowedfuncs; $question->disallow = $question->options->disallow; $question->answerprefix = $question->options->answerprefix; } return $question; } /** * Sets the existing values into the form for the question specific data. * * This method copies the data from the existing database record into the form fields as default * values for the various elements. * * @param $question the question object from the database being used to fill the form */ public function set_data($question) { // Check to see if there are any existing question options, if not then just call // the base class set data method and exit. if (!isset($question->options)) { return parent::set_data($question); } // Now we do exactly the same for the variables. $vars = $question->options->variables; // If we found any variables then loop over them using a numerical key to provide an index // to the arrays we need to access in the form. if (count($vars)) { $key = 0; foreach ($vars as $var) { // For every variable set the default values. $default_values['variable['.$key.']'] = $var->name; // Only set the min and max defaults if this variable has a range. if ($var->min!='') { $default_values['varmin['.$key.']'] = $var->min; $default_values['varmax['.$key.']'] = $var->max; } $key++; } } // Add the default values for the allowed functions controls. // First check to see if there are any allowed functions defined. if (count($question->options->allowedfuncs)>0) { // Clear the 'all functions' flag since functions are restricted. $default_values['allowedfuncs[all]']=0; // Loop over all the functions which the parser understands. foreach (qtype_algebra_parser::$functions as $func) { // For each function see if the function is in the allowed function // list and if so set the check box otherwise remove the check box. if (in_array($func, $question->options->allowedfuncs)) { $default_values['allowedfuncs['.$func.']']=1; } else { $default_values['allowedfuncs['.$func.']']=0; } } } else { // There are no allowed functions defined so all functions are allowed. $default_values['allowedfuncs[all]']=1; } // Add the default values to the question object in a form which the parent // set data method will be able to use to find the default values. $question = (object)((array)$question + $default_values); // Finally call the parent set data method to handle everything else. parent::set_data($question); } /** * Validates the form data ensuring there are no obvious errors in the submitted data. * * This method performs some basic sanity checks on the form data before it gets converted * into a database record. * * @param $data the data from the form which needs to be checked * @param $files some files - I don't know what this is for! - files defined in the form?? */ public function validation($data, $files) { // Call the base class validation method and keep any errors it generates. $errors = parent::validation($data, $files); // Regular expression string to match a number. $renumber='/([+-]*(([0-9]+\.[0-9]*)|([0-9]+)|(\.[0-9]+))|'. '(([0-9]+\.[0-9]*)|([0-9]+)|(\.[0-9]+))E([-+]?\d+))/A'; // Perform sanity checks on the variables. $vars = $data['variable']; // Create an array of defined variables. $varlist=array(); foreach ($vars as $key => $var) { $trimvar = trim($var); $trimmin = trim($data['varmin'][$key]); $trimmax = trim($data['varmax'][$key]); // Check that there is a valid variable name otherwise skip. if ($trimvar == '') { continue; } // Check that this variable does not have the same name as a function. if (in_array($trimvar, qtype_algebra_parser::$functions) or in_array($trimvar, qtype_algebra_parser::$specials)) { $errors['variable['.$key.']'] = get_string('illegalvarname', 'qtype_algebra', $trimvar); } // Check that this variable has not been defined before. if (in_array($trimvar, $varlist)) { $errors['variable['.$key.']'] = get_string('duplicatevar', 'qtype_algebra'); } else { // Add the variable to the list of defined variables. $varlist[]=$trimvar; } // If the comparison algorithm selected is evaluate then ensure that each variable // has a valid minimum and maximum defined. For the other types of comparison we can // ignore the range. if ($data['compareby']=='eval') { // Check that a minimum has been defined. if ($trimmin == '') { $errors['varmin['.$key.']'] = get_string('novarmin', 'qtype_algebra'); } else if (!preg_match($renumber, $trimmin)) { // If there is one check that it's a number. $errors['varmin['.$key.']'] = get_string('notanumber', 'qtype_algebra'); } if ($trimmax == '') { $errors['varmax['.$key.']'] = get_string('novarmax', 'qtype_algebra'); } else if (!preg_match($renumber, $trimmax)) { // If there is one check that it is a number. $errors['varmax['.$key.']'] = get_string('notanumber', 'qtype_algebra'); } // Check that the minimum is less that the maximum! if ((float)$trimmin > (float)$trimmax) { $errors['varmin['.$key.']'] = get_string('varmingtmax', 'qtype_algebra'); } } // End check for eval type. } // End loop over variables. // Check that at least one variable is defined. if (count($varlist)==0) { $errors['variable[0]'] = get_string('notenoughvars', 'qtype_algebra'); } // Now perform the sanity checks on the answers. // Create a parser which we will use to check that the answers are understandable. $p = new qtype_algebra_parser; $answers = $data['answer']; $answercount = 0; $maxgrade = false; // Create an empty array to store the used variables. $ansvars=array(); // Create an empty array to store the used functions. $ansfuncs=array(); // Loop over all the answers in the form. foreach ($answers as $key => $answer) { // Try to parse the answer string using the parser. If this fails it will // throw an exception which we catch to generate the associated error string // for the expression. try { $expr=$p->parse($answer); // Add any new variables to the list we are keeping. First we get the list // of variables in this answer. Then we get the array of variables which are // in this answer that are not in any previous answer (using array_diff). // Finally we merge this difference array with the list of all variables so far. $tmpvars=$expr->get_variables(); $ansvars=array_merge($ansvars, array_diff($tmpvars, $ansvars)); // Check that all the variables in this answer have been declared. // Do this by looking for a non-empty array to be returned from the array_diff // between the list of all declared variables and the variables in this answer. if ($d=array_diff($tmpvars, $varlist)) { $errors['answer['.$key.']'] = get_string('undefinedvar', 'qtype_algebra', "'".implode("', '", $d)."'"); } // Do the same for functions which we did for variables. $ansfuncs=array_merge($ansfuncs, array_diff($expr->get_functions(), $ansfuncs)); // Check that this is not an empty answer. if (!is_a($expr, "qtype_algebra_parser_nullterm")) { // Increase the number of answers. $answercount++; // Check to see if the answer has the maximum grade. if ($data['fraction'][$key] == 1) { $maxgrade = true; } } } catch (Exception $e) { $errors['answer['.$key.']']=$e->getMessage(); // Return here because subsequent errors may be wrong due to not counting the answer // which just failed to parse. return $errors; } } // Check that we have at least one answer. if ($answercount==0) { $errors['answer[0]'] = get_string('notenoughanswers', 'quiz', 1); } // Check that at least one question has the maximum possible grade. if ($maxgrade == false) { $errors['fraction[0]'] = get_string('fractionsnomax', 'question'); } // Check for variables which are defined but never used. // Do this by looking for a non-empty array to be returned from array_diff. if ($d=array_diff($varlist, $ansvars)) { // Loop over all the variables in the form. foreach ($vars as $key => $var) { $trimvar = trim($var); // If the variable is in the unused array then add the error message to that variable. if (in_array($trimvar, $d)) { $errors['variable['.$key.']'] = get_string('unusedvar', 'qtype_algebra'); } } } // Check that the tolerance is greater than or equal to zero. if ($data['tolerance']<0) { $errors['tolerance']=get_string('toleranceltzero', 'qtype_algebra'); } return $errors; } public function qtype() { return 'algebra'; } }