From 96bee2b33467a1ce54f88c77b8a867ef1f7a9a97 Mon Sep 17 00:00:00 2001 From: Jean-Michel Vedrine Date: Sun, 9 Sep 2012 16:54:35 +0200 Subject: First version of algebra question type initially written by Roger Moore for Moodle 2.3 --- edit_algebra_form.php | 378 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 edit_algebra_form.php (limited to 'edit_algebra_form.php') diff --git a/edit_algebra_form.php b/edit_algebra_form.php new file mode 100644 index 0000000..eae12bf --- /dev/null +++ b/edit_algebra_form.php @@ -0,0 +1,378 @@ + + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + * @package questionbank + * @subpackage questiontypes + */ + +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 answers and the number to add to avoid clutter. +// algebra questions will likely not have huge number of different answers... +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) { + // 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','eval'); + + + // 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 + */ + 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; + } + } + } + // There are no allowed functions defined so all functions are allowed + else { + $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'); + } + // If there is one check that it is a number + else if(!preg_match($renumber,$trimmin)) { + $errors['varmin['.$key.']'] = get_string('notanumber','qtype_algebra'); + } + if ($trimmax == '') { + $errors['varmax['.$key.']'] = get_string('novarmax','qtype_algebra'); + } + // If there is one check that it is a number + else if(!preg_match($renumber,$trimmax)) { + $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'; + } +} + -- cgit v1.2.3