From 83a15d025cb163509dcff124ed914058c826966f Mon Sep 17 00:00:00 2001 From: pichetp Date: Fri, 29 May 2009 22:42:22 +0000 Subject: [PATCH] MDL-17366 adding the calculatedsimple questiontype --- lang/en_utf8/qtype_calculatedsimple.php | 15 + .../edit_calculatedsimple_form.php | 715 ++++++++++++++++++ question/type/calculatedsimple/icon.gif | Bin 0 -> 78 bytes .../type/calculatedsimple/questiontype.php | 434 +++++++++++ 4 files changed, 1164 insertions(+) create mode 100644 lang/en_utf8/qtype_calculatedsimple.php create mode 100644 question/type/calculatedsimple/edit_calculatedsimple_form.php create mode 100644 question/type/calculatedsimple/icon.gif create mode 100644 question/type/calculatedsimple/questiontype.php diff --git a/lang/en_utf8/qtype_calculatedsimple.php b/lang/en_utf8/qtype_calculatedsimple.php new file mode 100644 index 0000000000..4fd496f080 --- /dev/null +++ b/lang/en_utf8/qtype_calculatedsimple.php @@ -0,0 +1,15 @@ +{x..} will be substituted by a numerical value from the generated values'; +$string['wildcardparam']= 'Wild cards parameters used to generate the items '; +$string['willconverttocalculated']= 'If set, the Save as new question will save as a new calculated question'; +?> \ No newline at end of file diff --git a/question/type/calculatedsimple/edit_calculatedsimple_form.php b/question/type/calculatedsimple/edit_calculatedsimple_form.php new file mode 100644 index 0000000000..c2be49a483 --- /dev/null +++ b/question/type/calculatedsimple/edit_calculatedsimple_form.php @@ -0,0 +1,715 @@ + QTYPES
";print_r($QTYPES);echo"

"; + // echo "

session

";print_r($SESSION);echo"

"; + // echo "

question

";print_r($question);echo"

"; + $this->regenerate = true; + $this->question = $question; + $this->qtypeobj =& $QTYPES[$this->question->qtype]; + //get the dataset definitions for this question + //coming here everytime even when using a NoSubmitButton + //so 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 + + // question->id == 0 so no stored datasets + // else get datasets + if ( "1" == optional_param('reload','', PARAM_INT )) { + // echo "

optional reload exist

"; + $this->reload = true ; + }else { + $this->reload = false ; + } + if(!$this->reload ){ // use database data as this is first pass + if ( !empty($question->id)) { + if (empty($question->options)) { + $this->get_question_options($question); + } + $this->datasetdefs = $this->qtypeobj->get_dataset_definitions($question->id, array()); + + if(!empty($this->datasetdefs)){ + foreach ($this->datasetdefs as $datasetdef) { + // Get maxnumber + if ($this->maxnumber == -1 || $datasetdef->itemcount < $this->maxnumber) { + $this->maxnumber = $datasetdef->itemcount; + } + } + foreach ($this->datasetdefs as $defid => $datasetdef) { + if (isset($datasetdef->id)) { + $this->datasetdefs[$defid]->items = $DB->get_records_sql( // Use number as key!! + " SELECT itemnumber, definition, id, value + FROM {question_dataset_items} + WHERE definition = ? ", array($datasetdef->id)); + } + } + } + $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 + // verify for the specific dataset values as the other parameters + // unints, 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. + $dummyform = new stdClass(); + $mandatorydatasets = array(); + + if ( $dummyform->answer =optional_param('answer')) { // there is always at least one answer... + $fraction = optional_param('fraction') ; + $feedback = optional_param('feedback') ; + $tolerance = optional_param('tolerance') ; + $tolerancetype = optional_param('tolerancetype') ; + $correctanswerlength = optional_param('correctanswerlength') ; + $correctanswerformat = optional_param('correctanswerformat') ; + + 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]->feedback = $feedback[$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); + } + } + } + // echo "

this->answer

";print_r($this->answer);echo"

"; + // echo "

this->answer

";print_r($this->nonemptyanswer);echo"

"; + $this->datasetdefs = array(); + // rebuild datasetdefs from old values + $olddef = optional_param('datasetdef'); + $oldoptions = optional_param('defoptions'); + $calcmin = optional_param('calcmin') ; + $calclength = optional_param('calclength') ; + $calcmax = optional_param('calcmax') ; + /* echo "

calcmin

";print_r(optional_param('calcmin'));echo"

"; + echo "

calcmax

";print_r(optional_param('calcmax'));echo"

"; + echo "

calclength

";print_r(optional_param('calclength'));echo"

";*/ + $newdatasetvalues = false ; + // echo "

olddef

";print_r(optional_param('datasetdef'));echo"

"; + + for($key = 1 ; $key <= sizeof($olddef) ; $key++) { + $def = $olddef[$key] ; + $this->datasetdefs[$def]= new stdClass ; + $this->datasetdefs[$def]->type = 1; + $this->datasetdefs[$def]->category = 0; + // $this->datasets[$key]->name = $datasetname; + $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 (ereg('^(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 ; + // echo "

new datasetdefs values ".$regs[2]."xx". $regs[3]."xx".$regs[4]."

"; + } + + } + $this->datasetdefs[$def]->options="uniform:".$this->datasetdefs[$def]->calcmin.":".$this->datasetdefs[$def]->calcmax.":".$this->datasetdefs[$def]->calclength; + } + + // echo "

this datasetdefs

";print_r($this->datasetdefs);echo"

"; + // detect new datasets + $newdataset = false ; + foreach ($mandatorydatasets as $datasetname) { + if (!isset($this->datasetdefs["1-0-$datasetname"])) { + // list($options, $selected) = + // $this->dataset_options($form, $datasetname); + $key = "1-0-$datasetname"; + $this->datasetdefs[$key]=new stdClass ;//"1-0-$datasetname"; + $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 ; + // echo "

new datasetdefs $datasetname

"; + // $form->dataset[$key]=$selected ; + //$key++; + }else { + $this->datasetdefs["1-0-$datasetname"]->name = $datasetname ; + } + } + // remove obsolete datasets + $datasettoremove = false; + foreach ($this->datasetdefs as $defkey => $datasetdef){ + if(!isset($datasetdef->name )){ + // echo "

dataset $defkey to remove

"; + $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')){ + $maxnumber = optional_param('selectadd') ; + foreach ($this->datasetdefs as $defid => $datasetdef) { + $datasetdef->itemcount = $maxnumber; + unset($datasetdef->items); + for ($numberadded =1 ; $numberadded <= $maxnumber; $numberadded++){ + $datasetitem = new stdClass; + // $datasetitem->definition = $datasetdef->id ; + $datasetitem->itemnumber = $numberadded; + $datasetitem->id = 0; + $datasetitem->value = $this->qtypeobj->generate_dataset_item($datasetdef->options); + $this->datasetdefs[$defid]->items[$numberadded]=$datasetitem ; + /* if (!$DB->insert_record('question_dataset_items', $datasetitem)) { + print_error('cannotinsert', 'question'); + }*/ + }//for number added + }// datasetsdefs end + $this->maxnumber = $maxnumber ; + // echo"

using create new items $this->maxnumber

"; + }else { + // Handle reload dataset items + // echo"

using optional to build

"; + if ( "" !=optional_param('definition')&& !($datasettoremove ||$newdataset ||$newdatasetvalues )){ + $i = 1; + $fromformdefinition = optional_param('definition'); + $fromformnumber = optional_param('number'); + $fromformitemid = optional_param('itemid'); + ksort($fromformdefinition); + // echo "

fromformdefinition

";print_r($fromformdefinition);echo"

"; + // echo "

fromformnumber

";print_r($fromformnumber);echo"

"; + + 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 ; + } + } + } + } + + // echo "

line 443 datasetdefs

";print_r($this->datasetdefs);echo"

"; + //$key = 0 ; + // if ( "" !=optional_param('answer')) echo "

optional answer exist

"; + // if ( "" !=optional_param('answer['.$key.']','', PARAM_NOTAGS)) echo "

optional $key exist

"; + // if ( "" !=optional_param('noanswer','', PARAM_INT )) echo "

optional noanswer exist

"; + + parent::question_edit_form($submiturl, $question, $category, $contexts, $formeditable); + } + + function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) { + $repeated = parent::get_per_answer_fields(&$mform, $label, $gradeoptions, $repeatedoptions, $answersoption); + $mform->setType('answer', PARAM_NOTAGS); + $addrepeated = array(); + $addrepeated[] =& $mform->createElement('text', 'tolerance', get_string('tolerance', 'qtype_calculated')); + $repeatedoptions['tolerance']['type'] = PARAM_NUMBER; + $repeatedoptions['tolerance']['default'] = 0.01; + $addrepeated[] =& $mform->createElement('select', 'tolerancetype', get_string('tolerancetype', 'quiz'), $this->qtypeobj->tolerance_types()); + $addrepeated[] =& $mform->createElement('select', 'correctanswerlength', get_string('correctanswershows', 'qtype_calculated'), range(0, 9)); + $repeatedoptions['correctanswerlength']['default'] = 2; + + $answerlengthformats = array('1' => get_string('decimalformat', 'quiz'), '2' => get_string('significantfiguresformat', 'quiz')); + $addrepeated[] =& $mform->createElement('select', 'correctanswerformat', get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats); + array_splice($repeated, 3, 0, $addrepeated); + $repeated[1]->setLabel(get_string('correctanswerformula', 'quiz').'='); + + return $repeated; + } + + /** + * Add question-type specific form fields. + * + * @param MoodleQuickForm $mform the form being built. + */ + function definition_inner(&$mform) { + global $QTYPES; + $this->qtypeobj =& $QTYPES[$this->qtype()]; + $strquestionlabel = $this->qtypeobj->comment_header($this->nonemptyanswer); + $label = get_string("sharedwildcards", "qtype_datasetdependent"); + $mform->addElement('hidden', 'initialcategory', 1); + $mform->addElement('hidden', 'reload', 1); +// $html2 = $this->qtypeobj->print_dataset_definitions_category($this->question); +// $mform->insertElementBefore($mform->createElement('static','listcategory',$label,$html2),'name'); + $addfieldsname='updatecategory'; + $addstring=get_string("updatecategory", "qtype_calculated"); + $mform->registerNoSubmitButton($addfieldsname); + +// $mform->insertElementBefore( $mform->createElement('submit', $addfieldsname, $addstring),'listcategory'); + + $creategrades = get_grade_options(); + $this->add_per_answer_fields($mform, get_string('answerhdr', 'qtype_calculated', '{no}'), + $creategrades->gradeoptions, 1, 1); + + $repeated = array(); + $repeated[] =& $mform->createElement('header', 'unithdr', get_string('unithdr', 'qtype_numerical', '{no}')); + + $repeated[] =& $mform->createElement('text', 'unit', get_string('unit', 'quiz')); + $mform->setType('unit', PARAM_NOTAGS); + + $repeated[] =& $mform->createElement('text', 'multiplier', get_string('multiplier', 'quiz')); + $mform->setType('multiplier', PARAM_NUMBER); + + if (isset($this->question->options)){ + $countunits = count($this->question->options->units); + } else { + $countunits = 0; + } + if ($this->question->formoptions->repeatelements){ + $repeatsatstart = $countunits + 1; + } else { + $repeatsatstart = $countunits; + } + $this->repeat_elements($repeated, $repeatsatstart, array(), 'nounits', 'addunits', 2, get_string('addmoreunitblanks', 'qtype_calculated', '{no}')); + + if ($mform->elementExists('multiplier[0]')){ + $firstunit =& $mform->getElement('multiplier[0]'); + $firstunit->freeze(); + $firstunit->setValue('1.0'); + $firstunit->setPersistantFreeze(true); + } + //hidden elements + // $mform->addElement('hidden', 'wizard', 'datasetdefinitions'); + // $mform->setType('wizard', PARAM_ALPHA); + // $mform->addElement('header', '', ''); + $label = "
".get_string('wildcardrole', 'qtype_calculatedsimple')."
"; + $mform->addElement('html', "
 
"); + $mform->addElement('html', $label);// explaining the role of datasets so other strings can be shortened + + $mform->addElement('submit', 'analyzequestion', get_string('findwildcards','qtype_calculatedsimple')); + $mform->registerNoSubmitButton('analyzequestion'); + $mform->closeHeaderBefore('analyzequestion'); + if ( "" != optional_param('analyzequestion','', PARAM_RAW)) { + + $this->wizarddisplay = true; + // echo "

session answer

";print_r($SESSION);echo"

"; + // echo "

session answer

";print_r($SESSION->calculated->questionform->answers);echo"

"; + + + }else { + $this->wizwarddisplay = false; + } + if ($this->maxnumber != -1){ + $this->noofitems = $this->maxnumber; + } else { + $this->noofitems = 0; + } + if(!empty($this->datasetdefs)){ + + $key = 0; + $mform->addElement('header', 'additemhdr', get_string('wildcardparam', 'qtype_calculatedsimple')); + $idx = 1; + if(!empty($this->datasetdefs)){ + $j = (($this->noofitems) * count($this->datasetdefs))+1;// + foreach ($this->datasetdefs as $defkey => $datasetdef){ + $mform->addElement('static', "na[$j]", get_string('param', 'qtype_datasetdependent', $datasetdef->name)); + $this->qtypeobj->custom_generator_tools_part($mform, $idx, $j); + $mform->addElement('hidden', "datasetdef[$idx]"); + $mform->setType("datasetdef[$idx]", PARAM_RAW); + $idx++; + $mform->addElement('static', "divider[$j]", '', '
'); + $j++; + } + } + $addoptions = Array(); + $addoptions['1']='1'; + for ($i=10; $i<=100 ; $i+=10){ + $addoptions["$i"]="$i"; + } + $mform->closeHeaderBefore('additemhdr'); + $addgrp = array(); + $addgrp[] =& $mform->createElement('submit', 'addbutton', get_string('generatenewitemsset', 'qtype_calculatedsimple')); + $addgrp[] =& $mform->createElement('select', "selectadd", get_string('additem', 'qtype_datasetdependent'), $addoptions); + $addgrp[] = & $mform->createElement('static',"stat","Items",get_string('item(s)', 'qtype_datasetdependent')); + $mform->addGroup($addgrp, 'addgrp', '', ' ', false); + $mform->registerNoSubmitButton('addbutton'); + $mform->closeHeaderBefore('addgrp'); + $mform->addElement('static', "divideradd", '', ''); + if ($this->noofitems == 0) { + $mform->addElement('static','warningnoitems','',''.get_string('youmustaddatleastoneitem', 'qtype_datasetdependent').''); + $mform->closeHeaderBefore('warningnoitems'); + }else { + $mform->addElement('header', 'additemhdr1', get_string('wildcarditems', 'qtype_calculatedsimple')); + $mform->closeHeaderBefore('additemhdr1'); + // $mform->addElement('header', '', get_string('itemno', 'qtype_datasetdependent', "")); + // $mform->addElement('submit', 'updatedatasets', get_string('updatedatasetparam', 'qtype_datasetdependent')); + // $mform->registerNoSubmitButton('updatedatasets'); + // $mform->setAdvanced("updatedatasets",true); + +//------------------------------------------------------------------------------------------------------------------------------ + $j = $this->noofitems * count($this->datasetdefs); + for ($i = $this->noofitems; $i >= 1 ; $i--){ + foreach ($this->datasetdefs as $defkey => $datasetdef){ + $mform->addElement('text', "number[$j]", get_string('param', 'qtype_datasetdependent', $datasetdef->name)); + $mform->setType("number[$j]", PARAM_NUMBER); + $mform->setAdvanced("number[$j]",true); + $mform->addElement('hidden', "itemid[$j]"); + $mform->setType("itemid[$j]", PARAM_INT); + + $mform->addElement('hidden', "definition[$j]"); + $mform->setType("definition[$j]", PARAM_NOTAGS); + + $j--; + } + if (!empty( $strquestionlabel)){ + $repeated[] =& $mform->addElement('static', "answercomment[$i]", "".get_string('itemno', 'qtype_datasetdependent', $i)."  ".$strquestionlabel); + } + $mform->addElement('static', "divider1[$j]", '', '
'); + + } + } + // if ($this->outsidelimit){ + // $mform->addElement('static','outsidelimit','',''); + // } + }else { + $mform->addElement('static','warningnowildcards','',''.get_string('atleastonewildcard', 'qtype_calculatedsimple').''); + $mform->closeHeaderBefore('warningnowildcards'); + } + +//------------------------------------------------------------------------------------------------------------------------------ + //non standard name for button element needed so not using add_action_buttons + //hidden elements + + $mform->addElement('hidden', 'id'); + $mform->setType('id', PARAM_INT); + + $mform->addElement('hidden', 'courseid'); + $mform->setType('courseid', PARAM_INT); + $mform->setDefault('courseid', 0); + + $mform->addElement('hidden', 'cmid'); + $mform->setType('cmid', PARAM_INT); + $mform->setDefault('cmid', 0); + if (!empty($this->question->id)){ + if ($this->question->formoptions->cansaveasnew){ + $mform->addElement('header', 'additemhdr', get_string('converttocalculated', 'qtype_calculatedsimple')); + $mform->closeHeaderBefore('additemhdr'); + + $mform->addElement('checkbox', 'convert','' ,get_string('willconverttocalculated', 'qtype_calculatedsimple')); + $mform->setDefault('convert', 0); + + } + } + // $mform->addElement('hidden', 'wizard', 'edit_calculatedsimple'); + // $mform->setType('wizard', PARAM_ALPHA); +/* + $mform->addElement('hidden', 'returnurl'); + $mform->setType('returnurl', PARAM_LOCALURL); + $mform->setDefault('returnurl', 0); + +*/ + } + + function set_data($question) { + $answer = $this->answer; + $default_values = array(); + if (count($answer)) { + $key = 0; + foreach ($answer as $answer){ + $default_values['answer['.$key.']'] = $answer->answer; + $default_values['fraction['.$key.']'] = $answer->fraction; + $default_values['tolerance['.$key.']'] = $answer->tolerance; + $default_values['tolerancetype['.$key.']'] = $answer->tolerancetype; + $default_values['correctanswerlength['.$key.']'] = $answer->correctanswerlength; + $default_values['correctanswerformat['.$key.']'] = $answer->correctanswerformat; + $default_values['feedback['.$key.']'] = $answer->feedback; + $key++; + } + } + if (isset($question->options)){ + $units = array_values($question->options->units); + // make sure the default unit is at index 0 + usort($units, create_function('$a, $b', + 'if (1.0 === (float)$a->multiplier) { return -1; } else '. + 'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }')); + if (count($units)) { + $key = 0; + foreach ($units as $unit){ + $default_values['unit['.$key.']'] = $unit->unit; + $default_values['multiplier['.$key.']'] = $unit->multiplier; + $key++; + } + } + } + $key = 0 ; + // echo "

mandatorydatasets $key xx".optional_param("answer[0]",'', PARAM_NOTAGS)."YY".$this->_form->getElementValue('answer['.$key.']')."xx

"; + + $formdata = array(); + $fromform = new stdClass(); + //fill out all data sets and also the fields for the next item to add. + if(!empty($this->datasetdefs)){ + $j = $this->noofitems * count($this->datasetdefs); + for ($itemnumber = $this->noofitems; $itemnumber >= 1; $itemnumber--){ + $data = array(); + foreach ($this->datasetdefs as $defid => $datasetdef){ + if (isset($datasetdef->items[$itemnumber])){ + $formdata["number[$j]"] = $datasetdef->items[$itemnumber]->value; + $formdata["definition[$j]"] = $defid; + $formdata["itemid[$j]"] = $datasetdef->items[$itemnumber]->id; + $data[$datasetdef->name] = $datasetdef->items[$itemnumber]->value; + } + $j--; + } + // echo "

answers avant comment

";print_r($answer);echo"

"; + // echo "

data avant comment

";print_r($data);echo"

"; + + if($this->noofitems != 0 ) { + if(!isset($question->id)) $question->id = 0 ; + $comment = $this->qtypeobj->comment_on_datasetitems($question->id,$this->nonemptyanswer, $data, $itemnumber);//$this-> + if ($comment->outsidelimit) { + $this->outsidelimit=$comment->outsidelimit ; + } + $totalcomment=''; + // echo "

comment

";print_r($comment);echo"

"; + + foreach ($this->nonemptyanswer as $key => $answer) { + $totalcomment .= $comment->stranswers[$key].'
'; + } + + $formdata['answercomment['.$itemnumber.']'] = $totalcomment ; + } + } + // $formdata['reload'] = '1'; + // $formdata['nextpageparam[forceregeneration]'] = $this->regenerate; + $formdata['selectdelete'] = '1'; + $formdata['selectadd'] = '1'; + $j = $this->noofitems * count($this->datasetdefs)+1; + $data = array(); // data for comment_on_datasetitems later + $idx =1 ; + foreach ($this->datasetdefs as $defid => $datasetdef){ + $formdata["datasetdef[$idx]"] = $defid; + $idx++; + } + $formdata = $this->qtypeobj->custom_generator_set_data($this->datasetdefs, $formdata); + } + $question = (object)((array)$question + $default_values+$formdata ); + // echo "

question data

";print_r($question);echo"

"; + + parent::set_data($question); + } + + function qtype() { + return 'calculatedsimple'; + } + + 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); + } + // echo "numericalquestion qtextremaining
";print_r($possibledatasets);
+        while  (ereg('\{=([^[: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'] .= '
'.$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){ + // $errors['questiontext']=get_string('atleastonewildcard', 'qtype_datasetdependent'); + 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'); + } + $numbers = $data['number']; + foreach ($numbers as $key => $number){ + if(! is_numeric($number)){ + if (stristr($number,',')){ + $errors['number['.$key.']'] = get_string('The , cannot be used, use . as in 0.013 or 1.3e-2', 'qtype_datasetdependent'); + }else { + $errors['number['.$key.']'] = get_string('This is not a valid number', 'qtype_datasetdependent'); + } + }else if( stristr($number,'x')){ + $errors['number['.$key.']'] = get_string('Hexadecimal format (i.e. 0X12d) is not allowed', 'qtype_datasetdependent'); + } else if( is_nan($number)){ + $errors['number['.$key.']'] = get_string('is a NAN number', 'qtype_datasetdependent'); + } + } + if ( $this->noofitems==0 ){ + $errors['warning'] = get_string('warning', 'mnet'); + } + + return $errors; + } +} +?> \ No newline at end of file diff --git a/question/type/calculatedsimple/icon.gif b/question/type/calculatedsimple/icon.gif new file mode 100644 index 0000000000000000000000000000000000000000..e3d60dffbb3ff299080c6c90168e8322377f1a8e GIT binary patch literal 78 zcmZ?wbhEHb6krfw_`t{j0g69aSQ!`?8FWBgkPHKpYEOSA!;Rz9<~-cHD`Ls?M|*0g bE}QbK)9G%e=B-_uo^mhUCVAhDk--`O>5mzv literal 0 HcmV?d00001 diff --git a/question/type/calculatedsimple/questiontype.php b/question/type/calculatedsimple/questiontype.php new file mode 100644 index 0000000000..7eedba4d5b --- /dev/null +++ b/question/type/calculatedsimple/questiontype.php @@ -0,0 +1,434 @@ +subtypeoptions; + // Get old answers: + global $CFG, $DB; + + if (isset($question->answer) && !isset($question->answers)) { + $question->answers = $question->answer; + } + + // Get old versions of the objects + if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { + $oldanswers = array(); + } + + if (!$oldoptions = $DB->get_records('question_calculated', array('question' => $question->id), 'answer ASC')) { + $oldoptions = array(); + } + + // Save the units. + $virtualqtype = $this->get_virtual_qtype(); + $result = $virtualqtype->save_numerical_units($question); + if (isset($result->error)) { + return $result; + } else { + $units = &$result->units; + } + // Insert all the new answers + if (isset($question->answer) && !isset($question->answers)) { + $question->answers=$question->answer; + } + foreach ($question->answers as $key => $dataanswer) { + if ( trim($dataanswer) != '' ) { + $answer = new stdClass; + $answer->question = $question->id; + $answer->answer = trim($dataanswer); + $answer->fraction = $question->fraction[$key]; + $answer->feedback = trim($question->feedback[$key]); + + if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it + $answer->id = $oldanswer->id; + if (! $DB->update_record("question_answers", $answer)) { + $result->error = get_string('errorupdatinganswer','question',$answer->id); + return $result; + } + } else { // This is a completely new answer + if (! $answer->id = $DB->insert_record("question_answers", $answer)) { + $result->error = get_string('errorinsertinganswer','question'); + return $result; + } + } + + // Set up the options object + if (!$options = array_shift($oldoptions)) { + $options = new stdClass; + } + $options->question = $question->id; + $options->answer = $answer->id; + $options->tolerance = trim($question->tolerance[$key]); + $options->tolerancetype = trim($question->tolerancetype[$key]); + $options->correctanswerlength = trim($question->correctanswerlength[$key]); + $options->correctanswerformat = trim($question->correctanswerformat[$key]); + + // Save options + if (isset($options->id)) { // reusing existing record + if (! $DB->update_record('question_calculated', $options)) { + $mess->name = $this->name(); + $mess->id = $options->id ; + $result->error = get_string('errorupdatingoptions','question',$mess); + // $result->error = "Could not update question calculated options! (id=$options->id)"; + return $result; + } + } else { // new options + if (! $DB->insert_record('question_calculated', $options)) { + $result->error = "Could not insert question calculated options!"; + return $result; + } + } + } + } + // delete old answer records + if (!empty($oldanswers)) { + foreach($oldanswers as $oa) { + $DB->delete_records('question_answers', array('id' => $oa->id)); + } + } + + // delete old answer records + if (!empty($oldoptions)) { + foreach($oldoptions as $oo) { + $DB->delete_records('question_calculated', array('id' => $oo->id)); + } + } + + + if( isset($question->import_process)&&$question->import_process){ + $this->import_datasets($question); + }else { + //save datasets and datatitems from form i.e in question + // $datasetdefs = $this->get_dataset_definitions($question->id, array()); + $question->dataset = $question->datasetdef ; + // $this->save_dataset_definitions($question); + // Save datasets + $datasetdefinitions = $this->get_dataset_definitions($question->id, $question->dataset); + $tmpdatasets = array_flip($question->dataset); + $defids = array_keys($datasetdefinitions); + $datasetdefs = array(); + foreach ($defids as $defid) { + $datasetdef = &$datasetdefinitions[$defid]; + if (isset($datasetdef->id)) { + if (!isset($tmpdatasets[$defid])) { + // This dataset is not used any more, delete it + $DB->delete_records('question_datasets', array('question' => $question->id, 'datasetdefinition' => $datasetdef->id)); + // if ($datasetdef->category == 0) { // Question local dataset + $DB->delete_records('question_dataset_definitions', array('id' => $datasetdef->id)); + $DB->delete_records('question_dataset_items', array('definition' => $datasetdef->id)); + // } + } + // This has already been saved or just got deleted + unset($datasetdefinitions[$defid]); + continue; + } + if (!$datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef)) { + print_error("cannotcreatedataset", 'question', '', $defid); + } + $datasetdefs[]= clone($datasetdef); + $questiondataset = new stdClass; + $questiondataset->question = $question->id; + $questiondataset->datasetdefinition = $datasetdef->id; + if (!$DB->insert_record('question_datasets', $questiondataset)) { + print_error('cannotcreaterelation', 'question', '', $name); + } + unset($datasetdefinitions[$defid]); + } + // Remove local obsolete datasets as well as relations + // to datasets in other categories: + if (!empty($datasetdefinitions)) { + foreach ($datasetdefinitions as $def) { + $DB->delete_records('question_datasets', array('question' => $question->id, 'datasetdefinition' => $def->id)); + if ($def->category == 0) { // Question local dataset + $DB->delete_records('question_dataset_definitions', array('id' => $def->id)); + $DB->delete_records('question_dataset_items', array('definition' => $def->id)); + } + } + } + $datasetdefs = $this->get_dataset_definitions($question->id, $question->dataset); + // echo "

datasetdefs save

";print_r($datasetdefs);echo"

"; + // Handle adding and removing of dataset items + $i = 1; + ksort($question->definition); + foreach ($question->definition as $key => $defid) { + $addeditem = new stdClass(); + $addeditem->definition = $datasetdefs[$defid]->id; + $addeditem->value = $question->number[$i]; + $addeditem->itemnumber = ceil($i / count($datasetdefs)); + if (empty($question->makecopy) && $question->itemid[$i]) { + // Reuse any previously used record + $addeditem->id = $question->itemid[$i]; + if (!$DB->update_record('question_dataset_items', $addeditem)) { + print_error('cannotupdateitem', 'question'); + } + } else { + if (!$DB->insert_record('question_dataset_items', $addeditem)) { + print_error('cannotinsert', 'question'); + } + } + $i++; + } + if (isset($addeditem->itemnumber) && $maxnumber < $addeditem->itemnumber){ + $maxnumber = $addeditem->itemnumber; + foreach ($datasetdefs as $key => $newdef) { + if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) { + $newdef->itemcount = $maxnumber; + // Save the new value for options + $DB->update_record('question_dataset_definitions', $newdef); + } + } + } + } + // Report any problems. + //convert to calculated + if(!empty($question->makecopy) && !empty($question->convert) ){ + // echo "

question save

";print_r($question);echo"

"; + if (!$DB->set_field('question', 'qtype', 'calculated', array('id'=> $question->id))) { + print_error('cannotupdateitem', 'question'); + } + } + if (!empty($result->notice)) { + return $result; + } + return true; + } + function finished_edit_wizard(&$form) { + return true ; //isset($form->backtoquiz); + } + + + + + /** + * this version save the available data at the different steps of the question editing process + * without using global $SESSION as storage between steps + * at the first step $wizardnow = 'question' + * when creating a new question + * when modifying a question + * when copying as a new question + * the general parameters and answers are saved using parent::save_question + * then the datasets are prepared and saved + * at the second step $wizardnow = 'datasetdefinitions' + * the datadefs final type are defined as private, category or not a datadef + * at the third step $wizardnow = 'datasetitems' + * the datadefs parameters and the data items are created or defined + * + * @param object question + * @param object $form + * @param int $course + * @param PARAM_ALPHA $wizardnow should be added as we are coming from question2.php + */ + function save_question($question, $form, $course) { + + $question = default_questiontype::save_question($question, $form, $course); + return $question; + } + + + function response_summary($question, $state, $length=80, $formatting=true) { + // The actual response is the bit after the hyphen + return substr($state->answer, strpos($state->answer, '-')+1, $length); + } + + function custom_generator_tools_part(&$mform, $idx, $j){ + + $minmaxgrp = array(); + $minmaxgrp[] =& $mform->createElement('text', "calcmin[$idx]", get_string('calcmin', 'qtype_datasetdependent')); + $minmaxgrp[] =& $mform->createElement('text', "calcmax[$idx]", get_string('calcmax', 'qtype_datasetdependent')); + $mform->addGroup($minmaxgrp, 'minmaxgrp', get_string('minmax', 'qtype_datasetdependent'), ' - ', false); + $mform->setType("calcmin[$idx]", PARAM_NUMBER); + $mform->setType("calcmax[$idx]", PARAM_NUMBER); + + $precisionoptions = range(0, 10); + $mform->addElement('select', "calclength[$idx]", get_string('calclength', 'qtype_datasetdependent'), $precisionoptions); + + $distriboptions = array('uniform' => get_string('uniform', 'qtype_datasetdependent'), 'loguniform' => get_string('loguniform', 'qtype_datasetdependent')); + $mform->addElement('hidden', "calcdistribution[$idx]", 'uniform'); + + + } + + function comment_header($answers) { + //$this->get_question_options($question); + $strheader = ""; + $delimiter = ''; + + // $answers = $question->options->answers; + // echo "

comments header answers

";print_r($answers);echo"

"; + + foreach ($answers as $key => $answer) { + /* if (is_string($answer)) { + $strheader .= $delimiter.$answer; + } else {*/ + $strheader .= $delimiter.$answer->answer; + // } + $delimiter = '


'; + } + return $strheader; + } + + function comment_on_datasetitems($questionid, $answers,$data, $number) { + global $DB; + $comment = new stdClass; + $comment->stranswers = array(); + $comment->outsidelimit = false ; + $comment->answers = array(); + /// Find a default unit: + if (!empty($questionid) && $unit = $DB->get_record('question_numerical_units', array('question'=> $questionid, 'multiplier' => 1.0))) { + $unit = $unit->unit; + } else { + $unit = ''; + } + + $answers = fullclone($answers); + $strmin = get_string('min', 'quiz'); + $strmax = get_string('max', 'quiz'); + $errors = ''; + $delimiter = ': '; + $virtualqtype = $this->get_virtual_qtype(); + foreach ($answers as $key => $answer) { + $formula = $this->substitute_variables($answer->answer,$data); + $formattedanswer = qtype_calculated_calculate_answer( + $answer->answer, $data, $answer->tolerance, + $answer->tolerancetype, $answer->correctanswerlength, + $answer->correctanswerformat, $unit); + if ( $formula === '*'){ + $answer->min = ' '; + $formattedanswer->answer = $answer->answer ; + }else { + eval('$answer->answer = '.$formula.';') ; + $virtualqtype->get_tolerance_interval($answer); + } + if ($answer->min === '') { + // This should mean that something is wrong + $comment->stranswers[$key] = " $formattedanswer->answer".'

'; + } else if ($formula === '*'){ + $comment->stranswers[$key] = $formula.' = '.get_string('anyvalue','qtype_calculated').'

'; + }else{ + $comment->stranswers[$key]= $formula.' = '.$formattedanswer->answer.'' ; + $comment->stranswers[$key] .= "
".$strmin. $delimiter.$answer->min.'---'; + $comment->stranswers[$key] .= $strmax.$delimiter.$answer->max; + $comment->stranswers[$key] .='
'; + $correcttrue->correct = $formattedanswer->answer ; + $correcttrue->true = $answer->answer ; + if ($formattedanswer->answer < $answer->min || $formattedanswer->answer > $answer->max){ + $comment->outsidelimit = true ; + $comment->answers[$key] = $key; + $comment->stranswers[$key] .=get_string('trueansweroutsidelimits','qtype_calculated',$correcttrue);//ERROR True answer '..' outside limits'; + }else { + $comment->stranswers[$key] .=get_string('trueanswerinsidelimits','qtype_calculated',$correcttrue);//' True answer :'.$calculated->trueanswer.' inside limits'; + } + $comment->stranswers[$key] .=''; + } + } + return fullclone($comment); + } + + function tolerance_types() { + return array('1' => get_string('relative', 'quiz'), + '2' => get_string('nominal', 'quiz'), + // '3' => get_string('geometric', 'quiz') + ); + } + + function dataset_options($form, $name, $mandatory=true,$renameabledatasets=false) { + // Takes datasets from the parent implementation but + // filters options that are currently not accepted by calculated + // It also determines a default selection... + //$renameabledatasets not implemented anmywhere + list($options, $selected) = $this->dataset_options_from_database($form, $name,'','qtype_calculated'); + // list($options, $selected) = $this->dataset_optionsa($form, $name); + + foreach ($options as $key => $whatever) { + if (!ereg('^1-', $key) && $key != '0') { + unset($options[$key]); + } + } + if (!$selected) { + if ($mandatory){ + $selected = "1-0-$name"; // Default + }else { + $selected = "0"; // Default + } + } + return array($options, $selected); + } + + +/** + * Runs all the code required to set up and save an essay question for testing purposes. + * Alternate DB table prefix may be used to facilitate data deletion. + */ + function generate_test($name, $courseid = null) { + global $DB; + list($form, $question) = parent::generate_test($name, $courseid); + $form->feedback = 1; + $form->multiplier = array(1, 1); + $form->shuffleanswers = 1; + $form->noanswers = 1; + $form->qtype ='calculatedsimple'; + $question->qtype ='calculatedsimple'; + $form->answers = array('{a} + {b}'); + $form->fraction = array(1); + $form->tolerance = array(0.01); + $form->tolerancetype = array(1); + $form->correctanswerlength = array(2); + $form->correctanswerformat = array(1); + $form->questiontext = "What is {a} + {b}?"; + + if ($courseid) { + $course = $DB->get_record('course', array('id'=> $courseid)); + } + + $new_question = $this->save_question($question, $form, $course); + + $dataset_form = new stdClass(); + $dataset_form->nextpageparam["forceregeneration"]= 1; + $dataset_form->calcmin = array(1 => 1.0, 2 => 1.0); + $dataset_form->calcmax = array(1 => 10.0, 2 => 10.0); + $dataset_form->calclength = array(1 => 1, 2 => 1); + $dataset_form->number = array(1 => 5.4 , 2 => 4.9); + $dataset_form->itemid = array(1 => '' , 2 => ''); + $dataset_form->calcdistribution = array(1 => 'uniform', 2 => 'uniform'); + $dataset_form->definition = array(1 => "1-0-a", + 2 => "1-0-b"); + $dataset_form->nextpageparam = array('forceregeneration' => false); + $dataset_form->addbutton = 1; + $dataset_form->selectadd = 1; + $dataset_form->courseid = $courseid; + $dataset_form->cmid = 0; + $dataset_form->id = $new_question->id; + $this->save_dataset_items($new_question, $dataset_form); + + return $new_question; + } +} +//// END OF CLASS //// + +////////////////////////////////////////////////////////////////////////// +//// INITIATION - Without this line the question type is not in use... /// +////////////////////////////////////////////////////////////////////////// +question_register_questiontype(new question_calculatedsimple_qtype()); + + +?> -- 2.39.5