Index: questiontype.php =================================================================== RCS file: /cvsroot/moodle/moodle/question/type/calculated/questiontype.php,v retrieving revision 1.110 diff -u -r1.110 questiontype.php --- questiontype.php 22 Nov 2010 09:52:45 -0000 1.110 +++ questiontype.php 29 Nov 2010 15:48:20 -0000 @@ -1838,7 +1838,300 @@ ? "$currentdatasetdef->type-$currentdatasetdef->category-$name" : ''); } + function is_current_categorydataset_can_use_moveto_categorydataset($currentcategorydatasetdef ,$movetocategorycategorydatasetdef){ + if (strcmp($currentcategorydatasetdef->options, $movetocategorycategorydatasetdef->options) != 0 ) { + return false ; + } + if ($currentcategorydatasetdef->itemcount != $movetocategorycategorydatasetdef->itemcount) { + return false ; + } + // compare definition + // get dataitems + $currentitems = $this->get_database_dataset_items($currentcategorydatasetdef->id); + $movetoitems = $this->get_database_dataset_items($movetocategorycategorydatasetdef->id); + $tolerance = (float) ("1.0e-".ini_get('precision')); + foreach($currentitems as $i => $currentitem){ + if(!($currentitem->value - $tolerance < $movetoitems[$i]->value && $movetoitems[$i]->value < $currentitem->value + $tolerance )){ + return false ; + } + } + return true ; + } + function get_datasetdef_generic_names (& $datasetdefs ){ + $generic_names = array(); + foreach($datasetdefs as $def){ + $regs = null ; + $def->initialname = $def->name ; + $postfixindex = 0; + if(preg_match('~__([1-9][0-9]*)~',$def->name , $regs)){ + $postfix = $regs[0]; + $postfixindex = $regs[1] ; + $def->initialname = substr($def->name,0,strlen($def->name)-strlen($regs[0])) ; + } + $generic_names[$def->initialname][$def->name] = $postfixindex ; + } + return $generic_names ; + } + + function move_one_category_dataset ( $datasetdef , &$question , $nametochange = '' ){ + global $CFG , $DB ; + // so create the new def and test concurrence + // if concurence let the following code solve the problem + if($nametochange != ''){ + $datasetdef->name = $nametochange ; + } + if( ! $DB->get_records_sql("SELECT * FROM {question_dataset_definitions} WHERE category = ? AND name = ?", + array($question->category, $datasetdef->name ))){ + $olddatasetid = $datasetdef->id ; + $olditemcount = $datasetdef->itemcount ; + $datasetdef->itemcount =0; + $datasetdef->category = $question->category ; + $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef); + // get the old items + $olditems = $this->get_database_dataset_items($olddatasetid); + //copy the dataitems + if (count($olditems) > 0 ) { + $itemcount = 0; + foreach($olditems as $item ){ + $item->definition = $datasetdef->id; + $DB->insert_record('question_dataset_items', $item); + $itemcount++; + } + //update item count to olditemcount if + // at least this number of items has been recover from the database + if( $olditemcount <= $itemcount ) { + $datasetdef->itemcount = $olditemcount; + } else { + $datasetdef->itemcount = $itemcount ; + } + $DB->update_record('question_dataset_definitions', $datasetdef); + } // end of copy the dataitems + // replace the link to the new dataset_definitions + $dataset = $DB->get_record('question_datasets', array('question' => $question->id, 'datasetdefinition'=> $olddatasetid)); + $dataset->datasetdefinition = $datasetdef->id; + if ( ! $DB->update_record('question_datasets',$dataset)){ + // todo error message or return false + } + // delete if no used by other questions + if( ! $datasets = $DB->get_records('question_datasets', array( 'datasetdefinition'=> $olddatasetid))){ + $DB->delete_records('question_dataset_definitions', array('id' => $olddatasetid)); + $DB->delete_records('question_dataset_items', array('definition' => $olddatasetid)); + } + return true ; + }else { + return false ; + } + } + /** + * If your question type has a datasets that are related to the question category + * e.g. calculated questiontype then you should set this to true so that + * the move_category_datasets() function wll be called when the question category is changed + * + * @return true or false. + */ + function has_category_datasets() { + return true ; + } + /** This function move category datasets to the new question category + * If there is already a dataset category with the same name then a new + * name is created using a postfix __123 (a number) to differentiate with the + * dataset category already in the moving to category. + * In the process the original {param}name in the question text and answers text is changed, so + * the moved question remain valid. + * The code used arrays to store the generic wild name (i.e.{c}) and the added postfix index + * in the question and in the move to category. + * There are four main options + * 1.If there is no dataset name in the moveto category with the same generic name, + * then a copy of the dataset is created in the new category i.e. move_one_category_dataset() + * 2.If there is at least one dataset in the moveto category with the same generic name which + * has identical data then the question use this dataset. + * If the real name is different(i.e {c__2}) then the names are changed in the question text + * and anwers. + * 3.If there is no identical name dataset in the moveto category + * Then a copy of the dataset is created in the new category i.e. move_one_category_dataset() + * 4.There is a similar name dataset in the moveto category which has different data. + * In a do loop, the __1.. postfix is incremented until the resulting name in neither in the + * possible wild card in the question text and answers and neither in the moveto category. + * Then a copy of the dataset is created in the new category i.e. move_one_category_dataset() + * and the names are changed in the question text and anwers. + * + */ + + + function move_category_datasets($question){ + global $CFG , $DB ; + $regs= array(); + if (empty($question->id)) { + return true ; + } + // if no category datasets return + $sql = "SELECT i.* + FROM {question_datasets} d, + {question_dataset_definitions} i + WHERE d.question = ? + AND d.datasetdefinition = i.id + AND i.category != 0 order by i.name + "; + if (!$questiondatasetdefs = $DB->get_records_sql($sql, array($question->id))) { + return true ; + } + // the + $questiongenericnames = $this->get_datasetdef_generic_names($questiondatasetdefs); + $tomovedatasetdefs = array(''); + $tonameindex = array(); + $this->get_question_options($question); + $datasetnamechanged = false ; + foreach ($questiondatasetdefs as $questiondef) { + // if def->category != question->category + if ($questiondef->category != 0 && $questiondef->category != $question->category){// if def->category != question->category + // get the initial name + // test if 'use it' is possible + $identicaldata = false ; + $identicalname = false ; + $nametochange = '' ; + $todo = '' ; + $notidenticalnames = array(); + // refresh the names in tomovecategory + $tomovedatasetdefs = $DB->get_records_sql( + "SELECT * FROM {question_dataset_definitions} WHERE category = ? order by name", array($question->category)) ; + foreach($tomovedatasetdefs as $key => $tomovedef){ + $tonameindex[$tomovedef->name] = $key ; + } + // define the tomove generic names + $togenericnames = $this->get_datasetdef_generic_names($tomovedatasetdefs); + // 1.If there is no dataset name in the moveto category with the same generic name, + // then a copy of the dataset is created in the new category i.e. move_one_category_dataset() + if(!isset($togenericnames[$questiondef->initialname])){ + // so we create a new category dataset and copy it + // we just check + $testdef = clone($questiondef); + $nametochange = '' ; + if ( $this->move_one_category_dataset ( $testdef , $question )){ + $todo = 'done'; + }else { // there was concurrence so not done + $todo = ''; + } + // 2.If there is at least one dataset in the moveto category with the same generic name + // which has identical data then the question use this dataset. + }else if(isset($togenericnames[$questiondef->initialname])){ + $testdef = clone($questiondef); + $identicaldata = false ; + $identicalname = false ; + // search identical data in to category datasets + foreach($togenericnames[$questiondef->initialname] as $toname => $toindex ){ + // register is there is one similar name dataset in moveto category + if ($toname == $questiondef->name )$identicalname = true ; + if( $this->is_current_categorydataset_can_use_moveto_categorydataset($questiondef ,$tomovedatasetdefs[$tonameindex[$toname]])){ + $identical = true ; + $todefindex = $tonameindex[$toname] ; + $todo = 'useit' ; + //so connect immediately, just in case + if($datasetdef = $DB->get_records('question_dataset_definitions', array('id' => $testdef->id))){ + // so use it + $dataset = $DB->get_record('question_datasets', array('question' => $question->id, 'datasetdefinition'=> $testdef->id)); + $dataset->datasetdefinition = $tomovedatasetdefs[$tonameindex[$toname]]->id; + if ( ! $DB->update_record('question_datasets',$dataset)){ + // todo error message or return false no more in 2.0 + } + // delete original category dataset if not used by other questions + if( ! $datasets = $DB->get_records('question_datasets', array( 'datasetdefinition'=> $testdef->id ))){ + $DB->delete_records('question_dataset_definitions', array('id' => $testdef->id)); + $DB->delete_records('question_dataset_items', array('definition' => $testdef->id)); + } + // notify to replace the name in the question texts + if($toname != $questiondef->name){ + $nametochange = $toname; + } + $todo = 'done'; + // echo "
no initial name so just move questiondef "; + break ; + }else { // the dataset was no more there + $todo = ''; + } + } else { + // not identicaldata names + $notidenticalnames[$toname] = $toindex ; + } + }// foreach($togeneric_names) + // if here it is because no similar data exits in moveto category dataset + // 3.If there is no identical name dataset in the moveto category + // Then a copy of the dataset is created in the new category i.e. move_one_category_dataset() + if( ! $identicalname && $todo == '' ){ + if( $this->move_one_category_dataset( $questiondef , $question )){ + $todo = 'done'; + }else { // there was concurrence so not done + $todo = ''; + } + } + }// end of isset in to generic_names + // if todo is not defined the only case left is that there no + // There is a similar name dataset in the moveto category which has different data. + // we must create a new one with a distinct name from all possible + // wildcards in the question and in the moveto category + if ( $todo == '' ){ + // search all possible datasetnames in + $alldatasetnames = $this->find_dataset_names($question->questiontext); + foreach ($question->options->answers as $answer) { + $alldatasetnames += $this->find_dataset_names($answer->answer); + } + $i = 0 ; + $imax = 0 ; // limit the do loop + $nametested = $questiondef->initialname ; + // not already used + $newname = '' ; + do { + if($i > 0 ){ + $nametested = $questiondef->initialname.'__'.$i ; + } + // testing if it is not the cause of being not identical or + // or undefined in the actual questiondef + if (! isset($notidenticalnames[$nametested]) && ! isset($alldatasetnames[$nametested] )) { + $nametochange = $nametested ; + $testdef = clone($questiondef); + if ( $this->move_one_category_dataset ( $testdef , $question ,$nametochange )){ + + $newname = $nametochange ; + } + } + $i++ ; + } while ($newname == '' && $imax < 2000 ); + $todo = 'copy' ; + if($newname != $questiondef->name){ + $nametochange = $newname; + } + $testdef = clone($questiondef); + $todo = 'move' ; + // so create the new def and test concurrence + // if concurence let the following code solve the problem + + } // then either same name but not identical or + // so we process + // auxiliary functions + if( $nametochange != '' ){ + // change the dataset name in question text and answers + $question->questiontext = str_replace('{'.$questiondef->name.'}', '{'.$nametochange.'}', $question->questiontext); + foreach ($question->options->answers as $answer) { + $answer->answer = str_replace('{'.$questiondef->name.'}', '{'.$nametochange.'}', $answer->answer); + } + $datasetnamechanged = true ; + // update the + } + }// if def->category != question->category + } //foreach ($questiondatasetdefs as $questiondef) + // update question data if there was a name changed + + if ($datasetnamechanged){ + $DB->set_field('question', 'questiontext', $question->questiontext ,array('id' => $question->id)); + foreach ($question->options->answers as $answer) { + $DB->set_field('question_answers', 'answer', $answer->answer,array('id' => $answer->id)); + } + if(isset($question->options->synchronize) && $question->options->synchronize == 2 ){ + $this->addnamecategory($question); + } + } + } + function find_dataset_names($text) { /// Returns the possible dataset names found in the text as an array /// The array has the dataset name for both key and value