|
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 00031 defined('MOODLE_INTERNAL') || die(); 00032 00033 require_once(dirname(__FILE__) . '/../type/questiontypebase.php'); 00034 00035 00044 abstract class question_bank { 00045 // TODO: This limit can be deleted if someday we move all TEXTS to BIG ones. MDL-19603 00046 const MAX_SUMMARY_LENGTH = 32000; 00047 00049 private static $questiontypes = array(); 00050 00052 private static $loadedqdefs = array(); 00053 00054 protected static $questionfinder = null; 00055 00057 private static $testmode = false; 00058 private static $testdata = array(); 00059 00060 private static $questionconfig = null; 00061 00069 private static $fractionoptions = null; 00071 private static $fractionoptionsfull = null; 00072 00077 public static function is_qtype_installed($qtypename) { 00078 $plugindir = get_plugin_directory('qtype', $qtypename); 00079 return $plugindir && is_readable($plugindir . '/questiontype.php'); 00080 } 00081 00089 public static function get_qtype($qtypename, $mustexist = true) { 00090 global $CFG; 00091 if (isset(self::$questiontypes[$qtypename])) { 00092 return self::$questiontypes[$qtypename]; 00093 } 00094 $file = get_plugin_directory('qtype', $qtypename) . '/questiontype.php'; 00095 if (!is_readable($file)) { 00096 if ($mustexist || $qtypename == 'missingtype') { 00097 throw new coding_exception('Unknown question type ' . $qtypename); 00098 } else { 00099 return self::get_qtype('missingtype'); 00100 } 00101 } 00102 include_once($file); 00103 $class = 'qtype_' . $qtypename; 00104 if (!class_exists($class)) { 00105 throw new coding_exception("Class $class must be defined in $file"); 00106 } 00107 self::$questiontypes[$qtypename] = new $class(); 00108 return self::$questiontypes[$qtypename]; 00109 } 00110 00115 public static function get_config() { 00116 if (is_null(self::$questionconfig)) { 00117 self::$questionconfig = get_config('question'); 00118 } 00119 return self::$questionconfig; 00120 } 00121 00126 public static function qtype_enabled($qtypename) { 00127 $config = self::get_config(); 00128 $enabledvar = $qtypename . '_disabled'; 00129 return self::qtype_exists($qtypename) && empty($config->$enabledvar) && 00130 self::get_qtype($qtypename)->menu_name() != ''; 00131 } 00132 00137 public static function qtype_exists($qtypename) { 00138 return array_key_exists($qtypename, get_plugin_list('qtype')); 00139 } 00140 00145 public static function get_qtype_name($qtypename) { 00146 return self::get_qtype($qtypename)->local_name(); 00147 } 00148 00152 public static function get_all_qtypes() { 00153 $qtypes = array(); 00154 foreach (get_plugin_list('qtype') as $plugin => $notused) { 00155 try { 00156 $qtypes[$plugin] = self::get_qtype($plugin); 00157 } catch (coding_exception $e) { 00158 // Catching coding_exceptions here means that incompatible 00159 // question types do not cause the rest of Moodle to break. 00160 } 00161 } 00162 return $qtypes; 00163 } 00164 00171 public static function sort_qtype_array($qtypes, $config = null) { 00172 if (is_null($config)) { 00173 $config = self::get_config(); 00174 } 00175 00176 $sortorder = array(); 00177 $otherqtypes = array(); 00178 foreach ($qtypes as $name => $localname) { 00179 $sortvar = $name . '_sortorder'; 00180 if (isset($config->$sortvar)) { 00181 $sortorder[$config->$sortvar] = $name; 00182 } else { 00183 $otherqtypes[$name] = $localname; 00184 } 00185 } 00186 00187 ksort($sortorder); 00188 collatorlib::asort($otherqtypes); 00189 00190 $sortedqtypes = array(); 00191 foreach ($sortorder as $name) { 00192 $sortedqtypes[$name] = $qtypes[$name]; 00193 } 00194 foreach ($otherqtypes as $name => $notused) { 00195 $sortedqtypes[$name] = $qtypes[$name]; 00196 } 00197 return $sortedqtypes; 00198 } 00199 00204 public static function get_creatable_qtypes() { 00205 $config = self::get_config(); 00206 $allqtypes = self::get_all_qtypes(); 00207 00208 $qtypenames = array(); 00209 foreach ($allqtypes as $name => $qtype) { 00210 if (self::qtype_enabled($name)) { 00211 $qtypenames[$name] = $qtype->local_name(); 00212 } 00213 } 00214 00215 $qtypenames = self::sort_qtype_array($qtypenames); 00216 00217 $creatableqtypes = array(); 00218 foreach ($qtypenames as $name => $notused) { 00219 $creatableqtypes[$name] = $allqtypes[$name]; 00220 } 00221 return $creatableqtypes; 00222 } 00223 00230 public static function load_question_definition_classes($qtypename) { 00231 global $CFG; 00232 if (isset(self::$loadedqdefs[$qtypename])) { 00233 return; 00234 } 00235 $file = $CFG->dirroot . '/question/type/' . $qtypename . '/question.php'; 00236 if (!is_readable($file)) { 00237 throw new coding_exception('Unknown question type (no definition) ' . $qtypename); 00238 } 00239 include_once($file); 00240 self::$loadedqdefs[$qtypename] = 1; 00241 } 00242 00251 public static function load_question($questionid, $allowshuffle = true) { 00252 global $DB; 00253 00254 if (self::$testmode) { 00255 // Evil, test code in production, but now way round it. 00256 return self::return_test_question_data($questionid); 00257 } 00258 00259 $questiondata = $DB->get_record_sql(' 00260 SELECT q.*, qc.contextid 00261 FROM {question} q 00262 JOIN {question_categories} qc ON q.category = qc.id 00263 WHERE q.id = :id', array('id' => $questionid), MUST_EXIST); 00264 get_question_options($questiondata); 00265 if (!$allowshuffle) { 00266 $questiondata->options->shuffleanswers = false; 00267 } 00268 return self::make_question($questiondata); 00269 } 00270 00277 public static function make_question($questiondata) { 00278 return self::get_qtype($questiondata->qtype, false)->make_question($questiondata, false); 00279 } 00280 00284 public static function get_finder() { 00285 if (is_null(self::$questionfinder)) { 00286 self::$questionfinder = new question_finder(); 00287 } 00288 return self::$questionfinder; 00289 } 00290 00294 public static function start_unit_test() { 00295 self::$testmode = true; 00296 } 00297 00301 public static function end_unit_test() { 00302 self::$testmode = false; 00303 self::$testdata = array(); 00304 } 00305 00306 private static function return_test_question_data($questionid) { 00307 if (!isset(self::$testdata[$questionid])) { 00308 throw new coding_exception('question_bank::return_test_data(' . $questionid . 00309 ') called, but no matching question has been loaded by load_test_data.'); 00310 } 00311 return self::$testdata[$questionid]; 00312 } 00313 00319 public static function load_test_question_data(question_definition $question) { 00320 if (!self::$testmode) { 00321 throw new coding_exception('question_bank::load_test_data called when ' . 00322 'not in test mode.'); 00323 } 00324 self::$testdata[$question->id] = $question; 00325 } 00326 00327 protected function ensure_fraction_options_initialised() { 00328 if (!is_null(self::$fractionoptions)) { 00329 return; 00330 } 00331 00332 // define basic array of grades. This list comprises all fractions of the form: 00333 // a. p/q for q <= 6, 0 <= p <= q 00334 // b. p/10 for 0 <= p <= 10 00335 // c. 1/q for 1 <= q <= 10 00336 // d. 1/20 00337 $rawfractions = array( 00338 0.9000000, 00339 0.8333333, 00340 0.8000000, 00341 0.7500000, 00342 0.7000000, 00343 0.6666667, 00344 0.6000000, 00345 0.5000000, 00346 0.4000000, 00347 0.3333333, 00348 0.3000000, 00349 0.2500000, 00350 0.2000000, 00351 0.1666667, 00352 0.1428571, 00353 0.1250000, 00354 0.1111111, 00355 0.1000000, 00356 0.0500000, 00357 ); 00358 00359 // Put the None option at the top. 00360 self::$fractionoptions = array( 00361 '0.0' => get_string('none'), 00362 '1.0' => '100%', 00363 ); 00364 self::$fractionoptionsfull = array( 00365 '0.0' => get_string('none'), 00366 '1.0' => '100%', 00367 ); 00368 00369 // The the positive grades in descending order. 00370 foreach ($rawfractions as $fraction) { 00371 $percentage = (100 * $fraction) . '%'; 00372 self::$fractionoptions["$fraction"] = $percentage; 00373 self::$fractionoptionsfull["$fraction"] = $percentage; 00374 } 00375 00376 // The the negative grades in descending order. 00377 foreach (array_reverse($rawfractions) as $fraction) { 00378 self::$fractionoptionsfull['' . (-$fraction)] = (-100 * $fraction) . '%'; 00379 } 00380 00381 self::$fractionoptionsfull['-1.0'] = '-100%'; 00382 } 00383 00391 public static function fraction_options() { 00392 self::ensure_fraction_options_initialised(); 00393 return self::$fractionoptions; 00394 } 00395 00397 public static function fraction_options_full() { 00398 self::ensure_fraction_options_initialised(); 00399 return self::$fractionoptionsfull; 00400 } 00401 } 00402 00403 00410 class question_finder { 00420 public function get_questions_from_categories($categoryids, $extraconditions, 00421 $extraparams = array()) { 00422 global $DB; 00423 00424 list($qcsql, $qcparams) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'qc'); 00425 00426 if ($extraconditions) { 00427 $extraconditions = ' AND (' . $extraconditions . ')'; 00428 } 00429 00430 return $DB->get_records_select_menu('question', 00431 "category $qcsql 00432 AND parent = 0 00433 AND hidden = 0 00434 $extraconditions", $qcparams + $extraparams, '', 'id,id AS id2'); 00435 } 00436 }