public function __construct($submiturl, $question, $category, $contexts, $formeditable = true) { $this->regenerate = true; $this->question = $question; $this->qtypeobj = question_bank::get_qtype($this->question->qtype); // Get the dataset definitions for this question. // Coming here everytime even when using a NoSubmitButton. // This will only set the values to the actual question database content // which is not what we want, so this should be removed from here. // Get priority to paramdatasets. $this->reload = optional_param('reload', false, PARAM_BOOL); if (!$this->reload) { // Use database data as this is first pass // Question->id == 0 so no stored datasets. if (!empty($question->id)) { $this->datasetdefs = $this->qtypeobj->get_dataset_definitions($question->id, array()); if (!empty($this->datasetdefs)) { foreach ($this->datasetdefs as $defid => $datasetdef) { // First get the items in case their number does not correspond to itemcount. if (isset($datasetdef->id)) { $this->datasetdefs[$defid]->items = $this->qtypeobj->get_database_dataset_items($datasetdef->id); if ($this->datasetdefs[$defid]->items != '') { $datasetdef->itemcount = count($this->datasetdefs[$defid]->items); } else { $datasetdef->itemcount = 0; } } // Get maxnumber. if ($this->maxnumber == -1 || $datasetdef->itemcount < $this->maxnumber) { $this->maxnumber = $datasetdef->itemcount; } } } $i = 0; foreach ($this->question->options->answers as $answer) { $this->answer[$i] = $answer; $i++; } $this->nonemptyanswer = $this->answer; } $datasettoremove = false; $newdatasetvalues = false; $newdataset = false; } else { // Handle reload to get values from the form-elements // answers, datasetdefs and data_items. In any case the validation // step will warn the user of any error in settings the values. // Verification for the specific dataset values as the other parameters // units, feeedback etc are handled elsewhere. // Handle request buttons : // 'analyzequestion' (Identify the wild cards {x..} present in answers). // 'addbutton' (create new set of datatitems). // 'updatedatasets' is handled automatically on each reload. // The analyzequestion is done every time on reload // to detect any new wild cards so that the current display reflects // the mandatory (i.e. in answers) datasets. // To implement : don't do any changes if the question is used in a quiz. // If new datadef, new properties should erase items. // most of the data. $datasettoremove = false; $newdatasetvalues = false; $newdataset = false; $dummyform = new stdClass(); $mandatorydatasets = array(); // Should not test on adding a new answer. // Should test if there are already olddatasets or if the 'analyzequestion'. // submit button has been clicked. if (optional_param_array('datasetdef', false, PARAM_BOOL) || optional_param('analyzequestion', false, PARAM_BOOL)) { if ($dummyform->answer = optional_param_array('answer', '', PARAM_NOTAGS)) { // There is always at least one answer... $fraction = optional_param_array('fraction', '', PARAM_FLOAT); $tolerance = optional_param_array('tolerance', '', PARAM_FLOAT); $tolerancetype = optional_param_array('tolerancetype', '', PARAM_FLOAT); $correctanswerlength = optional_param_array('correctanswerlength', '', PARAM_INT); $correctanswerformat = optional_param_array('correctanswerformat', '', PARAM_INT); foreach ($dummyform->answer as $key => $answer) { if (trim($answer) != '') { // Just look for non-empty. $this->answer[$key] = new stdClass(); $this->answer[$key]->answer = $answer; $this->answer[$key]->fraction = $fraction[$key]; $this->answer[$key]->tolerance = $tolerance[$key]; $this->answer[$key]->tolerancetype = $tolerancetype[$key]; $this->answer[$key]->correctanswerlength = $correctanswerlength[$key]; $this->answer[$key]->correctanswerformat = $correctanswerformat[$key]; $this->nonemptyanswer[] = $this->answer[$key]; $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer); } } } $this->datasetdefs = array(); // Rebuild datasetdefs from old values. if ($olddef = optional_param_array('datasetdef', '', PARAM_RAW)) { $calcmin = optional_param_array('calcmin', '', PARAM_FLOAT); $calclength = optional_param_array('calclength', '', PARAM_INT); $calcmax = optional_param_array('calcmax', '', PARAM_FLOAT); $oldoptions = optional_param_array('defoptions', '', PARAM_RAW); $newdatasetvalues = false; $sizeofolddef = count($olddef); for ($key = 1; $key <= $sizeofolddef; $key++) { $def = $olddef[$key]; $this->datasetdefs[$def] = new stdClass(); $this->datasetdefs[$def]->type = 1; $this->datasetdefs[$def]->category = 0; $this->datasetdefs[$def]->options = $oldoptions[$key]; $this->datasetdefs[$def]->calcmin = $calcmin[$key]; $this->datasetdefs[$def]->calcmax = $calcmax[$key]; $this->datasetdefs[$def]->calclength = $calclength[$key]; // Then compare with new values. if (preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~', $this->datasetdefs[$def]->options, $regs)) { if ($this->datasetdefs[$def]->calcmin != $regs[2] || $this->datasetdefs[$def]->calcmax != $regs[3] || $this->datasetdefs[$def]->calclength != $regs[4]) { $newdatasetvalues = true; } } $this->datasetdefs[$def]->options = "uniform:" . $this->datasetdefs[$def]->calcmin . ":" . $this->datasetdefs[$def]->calcmax . ":" . $this->datasetdefs[$def]->calclength; } } // Detect new datasets. $newdataset = false; foreach ($mandatorydatasets as $datasetname) { if (!isset($this->datasetdefs["1-0-{$datasetname}"])) { $key = "1-0-{$datasetname}"; $this->datasetdefs[$key] = new stdClass(); $this->datasetdefs[$key]->type = 1; $this->datasetdefs[$key]->category = 0; $this->datasetdefs[$key]->name = $datasetname; $this->datasetdefs[$key]->options = "uniform:1.0:10.0:1"; $newdataset = true; } else { $this->datasetdefs["1-0-{$datasetname}"]->name = $datasetname; } } // Remove obsolete datasets. $datasettoremove = false; foreach ($this->datasetdefs as $defkey => $datasetdef) { if (!isset($datasetdef->name)) { $datasettoremove = true; unset($this->datasetdefs[$defkey]); } } } } // Handle reload. // Create items if $newdataset and noofitems > 0 and !$newdatasetvalues. // Eliminate any items if $newdatasetvalues. // Eliminate any items if $datasettoremove, $newdataset, $newdatasetvalues. if ($datasettoremove || $newdataset || $newdatasetvalues) { foreach ($this->datasetdefs as $defkey => $datasetdef) { $datasetdef->itemcount = 0; unset($datasetdef->items); } } $maxnumber = -1; if (optional_param('addbutton', false, PARAM_BOOL)) { $maxnumber = optional_param('selectadd', '', PARAM_INT); // FIXME: sloppy coding. foreach ($this->datasetdefs as $defid => $datasetdef) { $datasetdef->itemcount = $maxnumber; unset($datasetdef->items); for ($numberadded = 1; $numberadded <= $maxnumber; $numberadded++) { $datasetitem = new stdClass(); $datasetitem->itemnumber = $numberadded; $datasetitem->id = 0; $datasetitem->value = $this->qtypeobj->generate_dataset_item($datasetdef->options); $this->datasetdefs[$defid]->items[$numberadded] = $datasetitem; } } $this->maxnumber = $maxnumber; } else { // Handle reload dataset items. if (optional_param_array('definition', '', PARAM_NOTAGS) && !($datasettoremove || $newdataset || $newdatasetvalues)) { $i = 1; $fromformdefinition = optional_param_array('definition', '', PARAM_NOTAGS); $fromformnumber = optional_param_array('number', '', PARAM_RAW); // This parameter will be validated in the form. $fromformitemid = optional_param_array('itemid', '', PARAM_INT); ksort($fromformdefinition); foreach ($fromformdefinition as $key => $defid) { $addeditem = new stdClass(); $addeditem->id = $fromformitemid[$i]; $addeditem->value = $fromformnumber[$i]; $addeditem->itemnumber = ceil($i / count($this->datasetdefs)); $this->datasetdefs[$defid]->items[$addeditem->itemnumber] = $addeditem; $this->datasetdefs[$defid]->itemcount = $i; $i++; } } if (isset($addeditem->itemnumber) && $this->maxnumber < $addeditem->itemnumber) { $this->maxnumber = $addeditem->itemnumber; if (!empty($this->datasetdefs)) { foreach ($this->datasetdefs as $datasetdef) { $datasetdef->itemcount = $this->maxnumber; } } } } parent::__construct($submiturl, $question, $category, $contexts, $formeditable); }
function validation($data, $files) { $errors = parent::validation($data, $files); //verifying for errors in {=...} in question text; $qtext = ""; $qtextremaining = $data['questiontext']; $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']); foreach ($possibledatasets as $name => $value) { $qtextremaining = str_replace('{' . $name . '}', '1', $qtextremaining); } while (preg_match('~\\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) { $qtextsplits = explode($regs1[0], $qtextremaining, 2); $qtext = $qtext . $qtextsplits[0]; $qtextremaining = $qtextsplits[1]; if (!empty($regs1[1]) && ($formulaerrors = qtype_calculated_find_formula_errors($regs1[1]))) { if (!isset($errors['questiontext'])) { $errors['questiontext'] = $formulaerrors . ':' . $regs1[1]; } else { $errors['questiontext'] .= '<br/>' . $formulaerrors . ':' . $regs1[1]; } } } $answers = $data['answer']; $answercount = 0; $maxgrade = false; $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']); $mandatorydatasets = array(); foreach ($answers as $key => $answer) { $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer); } if (count($mandatorydatasets) == 0) { foreach ($answers as $key => $answer) { $errors['answer[' . $key . ']'] = get_string('atleastonewildcard', 'qtype_datasetdependent'); } } foreach ($answers as $key => $answer) { //check no of choices // the * for everykind of answer not actually implemented $trimmedanswer = trim($answer); if ($trimmedanswer != '' || $answercount == 0) { $eqerror = qtype_calculated_find_formula_errors($trimmedanswer); if (FALSE !== $eqerror) { $errors['answer[' . $key . ']'] = $eqerror; } } if ($trimmedanswer != '') { if ('2' == $data['correctanswerformat'][$key] && '0' == $data['correctanswerlength'][$key]) { $errors['correctanswerlength[' . $key . ']'] = get_string('zerosignificantfiguresnotallowed', 'quiz'); } if (!is_numeric($data['tolerance'][$key])) { $errors['tolerance[' . $key . ']'] = get_string('mustbenumeric', 'qtype_calculated'); } if ($data['fraction'][$key] == 1) { $maxgrade = true; } $answercount++; } //check grades //TODO how should grade checking work here?? /*if ($answer != '') { if ($data['fraction'][$key] > 0) { $totalfraction += $data['fraction'][$key]; } if ($data['fraction'][$key] > $maxfraction) { $maxfraction = $data['fraction'][$key]; } }*/ } //grade checking : /// Perform sanity checks on fractional grades /*if ( ) { if ($maxfraction != 1) { $maxfraction = $maxfraction * 100; $errors['fraction[0]'] = get_string('errfractionsnomax', 'qtype_multichoice', $maxfraction); } } else { $totalfraction = round($totalfraction,2); if ($totalfraction != 1) { $totalfraction = $totalfraction * 100; $errors['fraction[0]'] = get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction); } }*/ $units = $data['unit']; if (count($units)) { foreach ($units as $key => $unit) { if (is_numeric($unit)) { $errors['unit[' . $key . ']'] = get_string('mustnotbenumeric', 'qtype_calculated'); } $trimmedunit = trim($unit); $trimmedmultiplier = trim($data['multiplier'][$key]); if (!empty($trimmedunit)) { if (empty($trimmedmultiplier)) { $errors['multiplier[' . $key . ']'] = get_string('youmustenteramultiplierhere', 'qtype_calculated'); } if (!is_numeric($trimmedmultiplier)) { $errors['multiplier[' . $key . ']'] = get_string('mustbenumeric', 'qtype_calculated'); } } } } if ($answercount == 0) { $errors['answer[0]'] = get_string('atleastoneanswer', 'qtype_calculated'); } if ($maxgrade == false) { $errors['fraction[0]'] = get_string('fractionsnomax', 'question'); } if (isset($data['backtoquiz']) && $this->noofitems == 0) { $errors['warning'] = get_string('warning', 'mnet'); } if ($this->outsidelimit) { // if(!isset($errors['warning'])) $errors['warning']=' '; $errors['outsidelimits'] = get_string('oneanswertrueansweroutsidelimits', 'qtype_calculated'); } /*Here we use the already done the error analysis so that * we could force all wild cards values display if there is an error in values. * as using a , in a number */ $numbers = $data['number']; foreach ($numbers as $key => $number) { if (!is_numeric($number)) { if (stristr($number, ',')) { $errors['number[' . $key . ']'] = get_string('notvalidnumber', 'qtype_datasetdependent'); } else { $errors['number[' . $key . ']'] = get_string('notvalidnumber', 'qtype_datasetdependent'); } } else { if (stristr($number, 'x')) { $errors['number[' . $key . ']'] = get_string('notvalidnumber', 'qtype_datasetdependent'); } else { if (is_nan($number)) { $errors['number[' . $key . ']'] = get_string('notvalidnumber', 'qtype_datasetdependent'); } } } } if ($this->noofitems == 0) { $errors['warning'] = get_string('warning', 'mnet'); } return $errors; }