|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 00039 class qtype_randomsamatch extends question_type { 00040 const MAX_SUBQUESTIONS = 10; 00041 00042 public function requires_qtypes() { 00043 return array('shortanswer', 'match'); 00044 } 00045 00046 public function is_usable_by_random() { 00047 return false; 00048 } 00049 00050 public function get_question_options($question) { 00051 global $DB; 00052 $question->options = $DB->get_record('question_randomsamatch', 00053 array('question' => $question->id), '*', MUST_EXIST); 00054 00055 // This could be included as a flag in the database. It's already 00056 // supported by the code. 00057 // Recurse subcategories: 0 = no recursion, 1 = recursion 00058 $question->options->subcats = 1; 00059 return true; 00060 00061 } 00062 00063 public function save_question_options($question) { 00064 global $DB; 00065 $options = new stdClass(); 00066 $options->question = $question->id; 00067 $options->choose = $question->choose; 00068 00069 if (2 > $question->choose) { 00070 $result = new stdClass(); 00071 $result->error = "At least two shortanswer questions need to be chosen!"; 00072 return $result; 00073 } 00074 00075 if ($existing = $DB->get_record('question_randomsamatch', 00076 array('question' => $options->question))) { 00077 $options->id = $existing->id; 00078 $DB->update_record('question_randomsamatch', $options); 00079 } else { 00080 $DB->insert_record('question_randomsamatch', $options); 00081 } 00082 return true; 00083 } 00084 00085 public function delete_question($questionid, $contextid) { 00086 global $DB; 00087 $DB->delete_records('question_randomsamatch', array('question' => $questionid)); 00088 00089 parent::delete_question($questionid, $contextid); 00090 } 00091 00092 public function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) { 00093 // Choose a random shortanswer question from the category: 00094 // We need to make sure that no question is used more than once in the 00095 // quiz. Therfore the following need to be excluded: 00096 // 1. All questions that are explicitly assigned to the quiz 00097 // 2. All random questions 00098 // 3. All questions that are already chosen by an other random question 00099 global $QTYPES, $OUTPUT, $USER; 00100 if (!isset($cmoptions->questionsinuse)) { 00101 $cmoptions->questionsinuse = $cmoptions->questions; 00102 } 00103 00104 if ($question->options->subcats) { 00105 // recurse into subcategories 00106 $categorylist = question_categorylist($question->category); 00107 } else { 00108 $categorylist = array($question->category); 00109 } 00110 00111 $saquestions = $this->get_sa_candidates($categorylist, $cmoptions->questionsinuse); 00112 00113 $count = count($saquestions); 00114 $wanted = $question->options->choose; 00115 00116 if ($count < $wanted) { 00117 $question->questiontext = "Insufficient selection options are 00118 available for this question, therefore it is not available in this 00119 quiz. Please inform your teacher."; 00120 // Treat this as a description from this point on 00121 $question->qtype = 'description'; 00122 return true; 00123 } 00124 00125 $saquestions = 00126 draw_rand_array($saquestions, $question->options->choose); // from bug 1889 00127 00128 foreach ($saquestions as $key => $wrappedquestion) { 00129 if (!$QTYPES[$wrappedquestion->qtype] 00130 ->get_question_options($wrappedquestion)) { 00131 return false; 00132 } 00133 00134 // Now we overwrite the $question->options->answers field to only 00135 // *one* (the first) correct answer. This loop can be deleted to 00136 // take all answers into account (i.e. put them all into the 00137 // drop-down menu. 00138 $foundcorrect = false; 00139 foreach ($wrappedquestion->options->answers as $answer) { 00140 if ($foundcorrect || $answer->fraction != 1.0) { 00141 unset($wrappedquestion->options->answers[$answer->id]); 00142 } else if (!$foundcorrect) { 00143 $foundcorrect = true; 00144 } 00145 } 00146 00147 if (!$QTYPES[$wrappedquestion->qtype] 00148 ->create_session_and_responses($wrappedquestion, $state, $cmoptions, 00149 $attempt)) { 00150 return false; 00151 } 00152 $wrappedquestion->name_prefix = $question->name_prefix; 00153 $wrappedquestion->maxgrade = $question->maxgrade; 00154 $cmoptions->questionsinuse .= ",$wrappedquestion->id"; 00155 $state->options->subquestions[$key] = clone($wrappedquestion); 00156 } 00157 00158 // Shuffle the answers (Do this always because this is a random question type) 00159 $subquestionids = array_values(array_map(create_function('$val', 00160 'return $val->id;'), $state->options->subquestions)); 00161 $subquestionids = swapshuffle($subquestionids); 00162 00163 // Create empty responses 00164 foreach ($subquestionids as $val) { 00165 $state->responses[$val] = ''; 00166 } 00167 return true; 00168 } 00169 00170 function restore_session_and_responses(&$question, &$state) { 00171 global $DB; 00172 global $QTYPES, $OUTPUT; 00173 static $wrappedquestions = array(); 00174 if (empty($state->responses[''])) { 00175 $question->questiontext = "Insufficient selection options are 00176 available for this question, therefore it is not available in this 00177 quiz. Please inform your teacher."; 00178 // Treat this as a description from this point on 00179 $question->qtype = 'description'; 00180 } else { 00181 $responses = explode(',', $state->responses['']); 00182 $responses = array_map(create_function('$val', 00183 'return explode("-", $val);'), $responses); 00184 00185 // Restore the previous responses 00186 $state->responses = array(); 00187 foreach ($responses as $response) { 00188 $wqid = $response[0]; 00189 $state->responses[$wqid] = $response[1]; 00190 if (!isset($wrappedquestions[$wqid])) { 00191 if (!$wrappedquestions[$wqid] = $DB->get_record('question', array('id' => $wqid))) { 00192 echo $OUTPUT->notification("Couldn't get question (id=$wqid)!"); 00193 return false; 00194 } 00195 if (!$QTYPES[$wrappedquestions[$wqid]->qtype] 00196 ->get_question_options($wrappedquestions[$wqid])) { 00197 echo $OUTPUT->notification("Couldn't get question options (id=$response[0])!"); 00198 return false; 00199 } 00200 00201 // Now we overwrite the $question->options->answers field to only 00202 // *one* (the first) correct answer. This loop can be deleted to 00203 // take all answers into account (i.e. put them all into the 00204 // drop-down menu. 00205 $foundcorrect = false; 00206 foreach ($wrappedquestions[$wqid]->options->answers as $answer) { 00207 if ($foundcorrect || $answer->fraction != 1.0) { 00208 unset($wrappedquestions[$wqid]->options->answers[$answer->id]); 00209 } else if (!$foundcorrect) { 00210 $foundcorrect = true; 00211 } 00212 } 00213 } 00214 $wrappedquestion = clone($wrappedquestions[$wqid]); 00215 00216 if (!$QTYPES[$wrappedquestion->qtype] 00217 ->restore_session_and_responses($wrappedquestion, $state)) { 00218 echo $OUTPUT->notification("Couldn't restore session of question (id=$response[0])!"); 00219 return false; 00220 } 00221 $wrappedquestion->name_prefix = $question->name_prefix; 00222 $wrappedquestion->maxgrade = $question->maxgrade; 00223 00224 $state->options->subquestions[$wrappedquestion->id] = 00225 clone($wrappedquestion); 00226 } 00227 } 00228 return true; 00229 } 00230 00231 public function get_sa_candidates($categorylist, $questionsinuse = 0) { 00232 global $DB; 00233 list ($usql, $params) = $DB->get_in_or_equal($categorylist); 00234 list ($ques_usql, $ques_params) = $DB->get_in_or_equal(explode(',', $questionsinuse), 00235 SQL_PARAMS_QM, null, false); 00236 $params = array_merge($params, $ques_params); 00237 return $DB->get_records_select('question', 00238 "qtype = 'shortanswer' " . 00239 "AND category $usql " . 00240 "AND parent = '0' " . 00241 "AND hidden = '0'" . 00242 "AND id $ques_usql", $params); 00243 } 00244 00251 public function get_random_guess_score($question) { 00252 return 1/$question->options->choose; 00253 } 00254 }