selectmanual = get_config('qtype_random', 'selectmanual'); } // Caches questions available as randoms sorted by category. // This is a 2d array. The first key is question category, and the // second is whether to include subcategories. function name() { return 'adaptive'; } function menu_name() { //This questiontype doesnt need to be included in the 'add new question' menu. return false; } function is_manual_graded() { return $this->selectmanual; } function is_manual_graded() { if (!$this->selectmanual) { return false; } // We take our best shot at working whether a particular question is manually // graded follows: We look to see if any of the questions that this random // question might select if of a manually graded type. If a category contains // a mixture of manual and non-manual questions, and if all the attempts so // far selected non-manual ones, this will give the wrong answer, but we // don't care. Even so, this is an expensive calculation! $this->init_qtype_lists(); if (!$this->manualqtypes) { return false; } if ($question->questiontext) { $categorylist = question_categorylist($question->category); } else { $categorylist = $question->category; } return record_exists_select('question', "category IN ($categorylist) AND parent = 0 AND hidden = 0 AND id NOT IN ($otherquestionsinuse) AND qtype IN ($this->manualqtypes)"); } function is_usable_by_adaptive() { return false; } // This method needs to be called before the -> excludedqtypes and // ->manualqtypes fields can be used. function init_qtype_lists() { global $QTYPES; if (is_null($this->excludedqtypes)) { $excludedqtypes = array(); $manualstypes = array(); foreach ($QTYPES as $qtype) { $quotedname = "'" . $qtype->name() . "'"; if (!$qtype->is_usable_by_adaptive()) { $excludedqtypes[] = $quotedname; } else if ($this->selectmanual && $qtype->is_manual_graded()) { $manualqtypes[] = $quotedname; } } $this->excludedqtypes = implode(',',$excludedqtypes); $this->manualqtypes = implode(',',$manualqtypes); } } function get_question_options(&$question) { //Do not specify, everything is handled by create_session return true; } //Adaptive questions get a question name that is Adaptive (categoryname) //This function is the centralised place to calculate that, given the category. function question_name($category) { return get_string('adaptive', 'quiz') .' ('. $category-> name .')'; } function save_question($question, $form, $course) { $form->name = ''; // Name is not a required field for random questions, but parent::save_question // assumes that it is. return parent::save_question($question,$form, $course); } function save_question_options($question) { //No options, but we set the parent field to the question's own id. //Setting the parent field has the effect of hiding this //question in various places. $updateobject = new stdClass; $updateobject->id = $question->id; $updateobject->parent = $question->id; //We also force the question name to be 'Adaptive (categoryname)'. if (!$category = get_record('question_categories', 'id', $question->category)) { error('Could retrieve question category'); } $updateobject->name = addlashes($this->question_name($category)); return update_record('question', $updateobject); } //get all usable questions from one category, and the correct subcategory // Questions should already be stored in respective subcategories, according to level **/ function get_usable_questions_from_category_start($categoryid, $subcategories, $questionsinuse) { $this->init_qtype_lists(); if ($subcategories) { $categorylist = question_categorylist($categoryid); } else { $categorylist = $categoryid; } //First, we only want to use the questions in subcategory 1 if (!$catadaptivesstart = get_records_select('question', "category IN ($categorylist) AND parent = 0 AND hidden = 0 AND count($arrgood) == 0 AND count($arrbad) == 0 AND $catname == '1' AND id NOT IN ($questionsinuse) AND qtype NOT IN ($this->excludedqtypes)", '', 'id')) { $catadaptivesstart = array(); } return $catadaptivesstart; } function get_usable_questions_from_category_goodrespons($categoryid, $subcategories, $questionsinuse) { $this->init_qtype_lists(); if ($subcategories) { $categorylist = question_categorylist($categoryid); } else { $categorylist = $categoryid; } //When a student answered correct 3 times, only questions from one category higher than the previous //one should be rendered. if (!$catadaptivegood = get_records_select('question', "category IN ($categorylist) AND parent = 0 AND hidden = 0 AND count($arrgood) == 3 /** or multiplication: modulus? AND count($arrbad) != 3 /** dito? AND id NOT IN ($questionsinuse) AND qtype NOT IN ($this->excludedqtypes)", '', 'id' )){ $catadaptivegood = array (); } return $catadaptivegood; } function get_usable_questions_from_category_badrespons($categoryid, $subcategories, $questionsinuse) { if ($subcategories) { $categorylist = question_categorylist($categoryid); } else { $categorylist = $categoryid; } //When a student answered correct 3 times, only questions from one subcategory lower than the previous // one should be rendered. if (!$catadaptivebad = get_records_select('question', "category IN ($categorylist) AND parent = 0 AND hidden = 0 AND count($arrgood) != 3 AND count($arrbad) ==3 /** or multiplication:modulus? AND id NOT IN ($questionsinuse) AND qtype NOT IN ($this->excludedqtypes)", '', 'id' )){ $catadaptivebad = array(); } return $catadaptivebad; } // Still needs work: for now, same as random questiontype.php function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) { global $QTYPES; // Choose a random question from the category: // We need to make sure that no question is used more than once in the // quiz. Therfore the following need to be excluded: // 1. All questions that are explicitly assigned to the quiz // 2. All random questions // 3. All questions that are already chosen by an other random question // 4. Deleted questions if (!isset($cmoptions->questionsinuse)) { $cmoptions->questionsinuse = $attempt->layout; } if (!isset($this->catrandoms[$question->category][$question->questiontext])) { $catrandoms = $this->get_usable_questions_from_category($question->category, $question->questiontext == "1", $cmoptions->questionsinuse); $this->catrandoms[$question->category][$question->questiontext] = swapshuffle_assoc($catrandoms); } while ($wrappedquestion = array_pop($this->catrandoms[$question->category][$question->questiontext])) { if (!ereg("(^|,)$wrappedquestion->id(,|$)", $cmoptions->questionsinuse)) { /// $randomquestion is not in use and will therefore be used /// as the randomquestion here... $wrappedquestion = get_record('question', 'id', $wrappedquestion->id); $QTYPES[$wrappedquestion->qtype] ->get_question_options($wrappedquestion); $QTYPES[$wrappedquestion->qtype] ->create_session_and_responses($wrappedquestion, $state, $cmoptions, $attempt); $wrappedquestion->name_prefix = $question->name_prefix; $wrappedquestion->maxgrade = $question->maxgrade; $cmoptions->questionsinuse .= ",$wrappedquestion->id"; $state->options->question = &$wrappedquestion; return true; } } $question->questiontext = ''. get_string('toomanyrandom', 'quiz'). ''; $question->qtype = 'description'; $state->responses = array('' => ''); return true; } function restore_session_and_responses(&$question, &$state) { /// The raw response records for random questions come in two flavours: /// ---- 1 ---- /// For responses stored by Moodle version 1.5 and later the answer /// field has the pattern random#-* where the # part is the numeric /// question id of the actual question shown in the quiz attempt /// and * represents the student response to that actual question. /// ---- 2 ---- /// For responses stored by older Moodle versions - the answer field is /// simply the question id of the actual question. The student response /// to the actual question is stored in a separate response record. /// ----------------------- /// This means that prior to Moodle version 1.5, random questions needed /// two response records for storing the response to a single question. /// From version 1.5 and later the question type random works like all /// the other question types in that it now only needs one response /// record per question. global $QTYPES; if (!ereg('^random([0-9]+)-(.*)$', $state->responses[''], $answerregs)) { if (empty($state->responses[''])) { // This is the case if there weren't enough questions available in the category. $question->questiontext = ''. get_string('toomanyrandom', 'quiz'). ''; $question->qtype = 'description'; return true; } // this must be an old-style state which stores only the id for the wrapped question if (!$wrappedquestion = get_record('question', 'id', $state->responses[''])) { notify("Can not find wrapped question {$state->responses['']}"); } // In the old model the actual response was stored in a separate entry in // the state table and fortunately there was only a single state per question if (!$state->responses[''] = get_field('question_states', 'answer', 'attempt', $state->attempt, 'question', $wrappedquestion->id)) { notify("Wrapped state missing"); } } else { if (!$wrappedquestion = get_record('question', 'id', $answerregs[1])) { // The teacher must have deleted this question by mistake // Convert it into a description type question with an explanation to the student $wrappedquestion = clone($question); $wrappedquestion->id = $answerregs[1]; $wrappedquestion->questiontext = get_string('questiondeleted', 'quiz'); $wrappedquestion->qtype = 'missingtype'; } $state->responses[''] = (false === $answerregs[2]) ? '' : $answerregs[2]; } if (!$QTYPES[$wrappedquestion->qtype] ->get_question_options($wrappedquestion)) { return false; } if (!$QTYPES[$wrappedquestion->qtype] ->restore_session_and_responses($wrappedquestion, $state)) { return false; } $wrappedquestion->name_prefix = $question->name_prefix; $wrappedquestion->maxgrade = $question->maxgrade; $state->options->question = &$wrappedquestion; return true; } function save_session_and_responses(&$question, &$state) { global $QTYPES; $wrappedquestion = &$state->options->question; // Trick the wrapped question into pretending to be the random one. $realqid = $wrappedquestion->id; $wrappedquestion->id = $question->id; $QTYPES[$wrappedquestion->qtype] ->save_session_and_responses($wrappedquestion, $state); // Read what the wrapped question has just set the answer field to // (if anything) $response = get_field('question_states', 'answer', 'id', $state->id); if(false === $response) { return false; } // Prefix the answer field... $response = "random$realqid-$response"; // ... and save it again. if (!set_field('question_states', 'answer', addslashes($response), 'id', $state->id)) { return false; } // Restore the real id $wrappedquestion->id = $realqid; return true; } function get_correct_responses(&$question, &$state) { global $QTYPES; $wrappedquestion = &$state->options->question; return $QTYPES[$wrappedquestion->qtype] ->get_correct_responses($wrappedquestion, $state); } // ULPGC ecastro function get_all_responses(&$question, &$state){ global $QTYPES; $wrappedquestion = &$state->options->question; return $QTYPES[$wrappedquestion->qtype] ->get_all_responses($wrappedquestion, $state); } // ULPGC ecastro function get_actual_response(&$question, &$state){ global $QTYPES; $wrappedquestion = &$state->options->question; return $QTYPES[$wrappedquestion->qtype] ->get_actual_response($wrappedquestion, $state); } function get_html_head_contributions(&$question, &$state) { global $QTYPES; $wrappedquestion = &$state->options->question; return $QTYPES[$wrappedquestion->qtype] ->get_html_head_contributions($wrappedquestion, $state); } function print_question(&$question, &$state, &$number, $cmoptions, $options) { global $QTYPES; $wrappedquestion = &$state->options->question; $wrappedquestion->randomquestionid = $question->id; $QTYPES[$wrappedquestion->qtype] ->print_question($wrappedquestion, $state, $number, $cmoptions, $options); } function grade_responses(&$question, &$state, $cmoptions) { global $QTYPES; $wrappedquestion = &$state->options->question; return $QTYPES[$wrappedquestion->qtype] ->grade_responses($wrappedquestion, $state, $cmoptions); } function get_texsource(&$question, &$state, $cmoptions, $type) { global $QTYPES; $wrappedquestion = &$state->options->question; return $QTYPES[$wrappedquestion->qtype] ->get_texsource($wrappedquestion, $state, $cmoptions, $type); } function compare_responses(&$question, $state, $teststate) { global $QTYPES; $wrappedquestion = &$teststate->options->question; return $QTYPES[$wrappedquestion->qtype] ->compare_responses($wrappedquestion, $state, $teststate); } function restore_recode_answer($state, $restore) { // The answer looks like 'randomXX-ANSWER', where XX is // the id of the used question and ANSWER the actual // response to that question. // However, there may still be old-style states around, // which store the id of the wrapped question in the // state of the random question and store the response // in a separate state for the wrapped question global $QTYPES; $answer_field = ""; if (ereg('^random([0-9]+)-(.*)$', $state->answer, $answerregs)) { // Recode the question id in $answerregs[1] // Get the question from backup_ids if(!$wrapped = backup_getid($restore->backup_unique_code,"question",$answerregs[1])) { echo 'Could not recode question in random-'.$answerregs[1].'
'; return($answer_field); } // Get the question type for recursion if (!$wrappedquestion->qtype = get_field('question', 'qtype', 'id', $wrapped->new_id)) { echo 'Could not get qtype while recoding question random-'.$answerregs[1].'
'; return($answer_field); } $newstate = $state; $newstate->question = $wrapped->new_id; $newstate->answer = $answerregs[2]; $answer_field = 'random'.$wrapped->new_id.'-'; // Recode the answer field in $answerregs[2] depending on // the qtype of question with id $answerregs[1] $answer_field .= $QTYPES[$wrappedquestion->qtype]->restore_recode_answer($newstate, $restore); } else { // Handle old-style states $answer_link = backup_getid($restore->backup_unique_code,"question",$state->answer); if ($answer_link) { $answer_field = $answer_link->new_id; } } return $answer_field; } } //// END OF CLASS //// ////////////////////////////////////////////////////////////////////////// //// INITIATION - Without this line the question type is not in use... /// ////////////////////////////////////////////////////////////////////////// question_register_questiontype(new random_qtype()); php?> /** retrieve question, according to subcategory **/ /** First, check subcategory **/ /** With empty arrays: subcategory name should be '1' **/ get_name /** With a full array: if array_good has 3 or a multiplication of 3 entries, move to subcategory +1 **/ get_name /** With a full array: if array_bad has 3 or a multiplication of 3 entries, move to subcategory -1 **/ get_name /** check whether this question is available **/ usable questions /** publish question to student **/ /** storing results: if the student was correct, store result in array_good **/ /** storing results: if the student was wrong, store result in array_bad **/ /** end: if there are no more usable questions, check subcategory of latest question. **/ /** end: if the student has completed 42 questions, check subcategory of latest question. **/ get_name /** if the name is 1, send student to beginner-level within course-category**/ /** if the name is 2, send student to elementair-level within course-category**/ /** if the name is 3, send student to intermediair-level within course-category**/