Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/question/engine/lib.php
Go to the documentation of this file.
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 require_once(dirname(__FILE__) . '/questionusage.php');
00030 require_once(dirname(__FILE__) . '/questionattempt.php');
00031 require_once(dirname(__FILE__) . '/questionattemptstep.php');
00032 require_once(dirname(__FILE__) . '/states.php');
00033 require_once(dirname(__FILE__) . '/datalib.php');
00034 require_once(dirname(__FILE__) . '/renderer.php');
00035 require_once(dirname(__FILE__) . '/bank.php');
00036 require_once(dirname(__FILE__) . '/../type/questiontypebase.php');
00037 require_once(dirname(__FILE__) . '/../type/questionbase.php');
00038 require_once(dirname(__FILE__) . '/../type/rendererbase.php');
00039 require_once(dirname(__FILE__) . '/../behaviour/behaviourbase.php');
00040 require_once(dirname(__FILE__) . '/../behaviour/rendererbase.php');
00041 require_once($CFG->libdir . '/questionlib.php');
00042 
00043 
00054 abstract class question_engine {
00056     private static $loadedbehaviours = array();
00057 
00067     public static function make_questions_usage_by_activity($component, $context) {
00068         return new question_usage_by_activity($component, $context);
00069     }
00070 
00076     public static function load_questions_usage_by_activity($qubaid) {
00077         $dm = new question_engine_data_mapper();
00078         return $dm->load_questions_usage_by_activity($qubaid);
00079     }
00080 
00087     public static function save_questions_usage_by_activity(question_usage_by_activity $quba) {
00088         $dm = new question_engine_data_mapper();
00089         $observer = $quba->get_observer();
00090         if ($observer instanceof question_engine_unit_of_work) {
00091             $observer->save($dm);
00092         } else {
00093             $dm->insert_questions_usage_by_activity($quba);
00094         }
00095     }
00096 
00101     public static function delete_questions_usage_by_activity($qubaid) {
00102         self::delete_questions_usage_by_activities(new qubaid_list(array($qubaid)));
00103     }
00104 
00109     public static function delete_questions_usage_by_activities(qubaid_condition $qubaids) {
00110         $dm = new question_engine_data_mapper();
00111         $dm->delete_questions_usage_by_activities($qubaids);
00112     }
00113 
00121     public static function set_max_mark_in_attempts(qubaid_condition $qubaids,
00122             $slot, $newmaxmark) {
00123         $dm = new question_engine_data_mapper();
00124         $dm->set_max_mark_in_attempts($qubaids, $slot, $newmaxmark);
00125     }
00126 
00133     public static function questions_in_use(array $questionids, qubaid_condition $qubaids = null) {
00134         if (is_null($qubaids)) {
00135             return false;
00136         }
00137         $dm = new question_engine_data_mapper();
00138         return $dm->questions_in_use($questionids, $qubaids);
00139     }
00140 
00149     public static function make_archetypal_behaviour($preferredbehaviour, question_attempt $qa) {
00150         self::load_behaviour_class($preferredbehaviour);
00151         $class = 'qbehaviour_' . $preferredbehaviour;
00152         if (!constant($class . '::IS_ARCHETYPAL')) {
00153             throw new coding_exception('The requested behaviour is not actually ' .
00154                     'an archetypal one.');
00155         }
00156         return new $class($qa, $preferredbehaviour);
00157     }
00158 
00164     public static function get_behaviour_unused_display_options($behaviour) {
00165         self::load_behaviour_class($behaviour);
00166         $class = 'qbehaviour_' . $behaviour;
00167         if (!method_exists($class, 'get_unused_display_options')) {
00168             return question_behaviour::get_unused_display_options();
00169         }
00170         return call_user_func(array($class, 'get_unused_display_options'));
00171     }
00172 
00186     public static function make_behaviour($behaviour, question_attempt $qa, $preferredbehaviour) {
00187         try {
00188             self::load_behaviour_class($behaviour);
00189         } catch (Exception $e) {
00190             self::load_behaviour_class('missing');
00191             return new qbehaviour_missing($qa, $preferredbehaviour);
00192         }
00193         $class = 'qbehaviour_' . $behaviour;
00194         return new $class($qa, $preferredbehaviour);
00195     }
00196 
00203     public static function load_behaviour_class($behaviour) {
00204         global $CFG;
00205         if (isset(self::$loadedbehaviours[$behaviour])) {
00206             return;
00207         }
00208         $file = $CFG->dirroot . '/question/behaviour/' . $behaviour . '/behaviour.php';
00209         if (!is_readable($file)) {
00210             throw new coding_exception('Unknown question behaviour ' . $behaviour);
00211         }
00212         include_once($file);
00213         self::$loadedbehaviours[$behaviour] = 1;
00214     }
00215 
00224     public static function get_archetypal_behaviours() {
00225         $archetypes = array();
00226         $behaviours = get_plugin_list('qbehaviour');
00227         foreach ($behaviours as $behaviour => $notused) {
00228             if (self::is_behaviour_archetypal($behaviour)) {
00229                 $archetypes[$behaviour] = self::get_behaviour_name($behaviour);
00230             }
00231         }
00232         asort($archetypes, SORT_LOCALE_STRING);
00233         return $archetypes;
00234     }
00235 
00240     public static function is_behaviour_archetypal($behaviour) {
00241         self::load_behaviour_class($behaviour);
00242         $plugin = 'qbehaviour_' . $behaviour;
00243         return constant($plugin . '::IS_ARCHETYPAL');
00244     }
00245 
00256     public static function sort_behaviours($archetypes, $orderlist, $disabledlist, $current=null) {
00257 
00258         // Get disabled behaviours
00259         if ($disabledlist) {
00260             $disabled = explode(',', $disabledlist);
00261         } else {
00262             $disabled = array();
00263         }
00264 
00265         if ($orderlist) {
00266             $order = explode(',', $orderlist);
00267         } else {
00268             $order = array();
00269         }
00270 
00271         foreach ($disabled as $behaviour) {
00272             if (array_key_exists($behaviour, $archetypes) && $behaviour != $current) {
00273                 unset($archetypes[$behaviour]);
00274             }
00275         }
00276 
00277         // Get behaviours in preferred order
00278         $behaviourorder = array();
00279         foreach ($order as $behaviour) {
00280             if (array_key_exists($behaviour, $archetypes)) {
00281                 $behaviourorder[$behaviour] = $archetypes[$behaviour];
00282             }
00283         }
00284         // Get the rest of behaviours and sort them alphabetically
00285         $leftover = array_diff_key($archetypes, $behaviourorder);
00286         asort($leftover, SORT_LOCALE_STRING);
00287 
00288         // Set up the final order to be displayed
00289         return $behaviourorder + $leftover;
00290     }
00291 
00299     public static function get_behaviour_options($currentbehaviour) {
00300         $config = question_bank::get_config();
00301         $archetypes = self::get_archetypal_behaviours();
00302 
00303         // If no admin setting return all behavious
00304         if (empty($config->disabledbehaviours) && empty($config->behavioursortorder)) {
00305             return $archetypes;
00306         }
00307 
00308         if (empty($config->behavioursortorder)) {
00309             $order = '';
00310         } else {
00311             $order = $config->behavioursortorder;
00312         }
00313         if (empty($config->disabledbehaviours)) {
00314             $disabled = '';
00315         } else {
00316             $disabled = $config->disabledbehaviours;
00317         }
00318 
00319         return self::sort_behaviours($archetypes, $order, $disabled, $currentbehaviour);
00320     }
00321 
00327     public static function get_behaviour_name($behaviour) {
00328         return get_string('pluginname', 'qbehaviour_' . $behaviour);
00329     }
00330 
00334     public static function get_all_response_file_areas() {
00335         $variables = array();
00336         foreach (question_bank::get_all_qtypes() as $qtype) {
00337             $variables += $qtype->response_file_areas();
00338         }
00339 
00340         $areas = array();
00341         foreach (array_unique($variables) as $variable) {
00342             $areas[] = 'response_' . $variable;
00343         }
00344         return $areas;
00345     }
00346 
00352     public static function get_dp_options() {
00353         return question_display_options::get_dp_options();
00354     }
00355 
00359     public static function initialise_js() {
00360         return question_flags::initialise_js();
00361     }
00362 }
00363 
00364 
00378 class question_display_options {
00380     const HIDDEN = 0;
00381     const VISIBLE = 1;
00382     const EDITABLE = 2;
00386     const MAX_ONLY = 1;
00387     const MARK_AND_MAX = 2;
00395     const MAX_DP = 7;
00396 
00401     public $readonly = false;
00402 
00407     public $clearwrong = false;
00408 
00417     public $correctness = self::VISIBLE;
00418 
00424     public $marks = self::MARK_AND_MAX;
00425 
00427     public $markdp = 2;
00428 
00435     public $flags = self::VISIBLE;
00436 
00442     public $feedback = self::VISIBLE;
00443 
00451     public $numpartscorrect = self::VISIBLE;
00452 
00458     public $generalfeedback = self::VISIBLE;
00459 
00466     public $rightanswer = self::VISIBLE;
00467 
00475     public $manualcomment = self::VISIBLE;
00476 
00482     public $manualcommentlink = null;
00483 
00489     public $questionreviewlink = null;
00490 
00496     public $history = self::HIDDEN;
00497 
00510     public $editquestionparams = array();
00511 
00515     public $context;
00516 
00522     public function hide_all_feedback() {
00523         $this->feedback = self::HIDDEN;
00524         $this->numpartscorrect = self::HIDDEN;
00525         $this->generalfeedback = self::HIDDEN;
00526         $this->rightanswer = self::HIDDEN;
00527         $this->manualcomment = self::HIDDEN;
00528         $this->correctness = self::HIDDEN;
00529     }
00530 
00540     public static function get_dp_options() {
00541         $options = array();
00542         for ($i = 0; $i <= self::MAX_DP; $i += 1) {
00543             $options[$i] = $i;
00544         }
00545         return $options;
00546     }
00547 }
00548 
00549 
00556 abstract class question_flags {
00565     protected static function get_toggle_checksum($qubaid, $questionid,
00566             $qaid, $slot, $user = null) {
00567         if (is_null($user)) {
00568             global $USER;
00569             $user = $USER;
00570         }
00571         return md5($qubaid . "_" . $user->secret . "_" . $questionid . "_" . $qaid . "_" . $slot);
00572     }
00573 
00579     public static function get_postdata(question_attempt $qa) {
00580         $qaid = $qa->get_database_id();
00581         $qubaid = $qa->get_usage_id();
00582         $qid = $qa->get_question()->id;
00583         $slot = $qa->get_slot();
00584         $checksum = self::get_toggle_checksum($qubaid, $qid, $qaid, $slot);
00585         return "qaid=$qaid&qubaid=$qubaid&qid=$qid&slot=$slot&checksum=$checksum&sesskey=" .
00586                 sesskey() . '&newstate=';
00587     }
00588 
00599     public static function update_flag($qubaid, $questionid, $qaid, $slot, $checksum, $newstate) {
00600         // Check the checksum - it is very hard to know who a question session belongs
00601         // to, so we require that checksum parameter is matches an md5 hash of the
00602         // three ids and the users username. Since we are only updating a flag, that
00603         // probably makes it sufficiently difficult for malicious users to toggle
00604         // other users flags.
00605         if ($checksum != self::get_toggle_checksum($qubaid, $questionid, $qaid, $slot)) {
00606             throw new moodle_exception('errorsavingflags', 'question');
00607         }
00608 
00609         $dm = new question_engine_data_mapper();
00610         $dm->update_question_attempt_flag($qubaid, $questionid, $qaid, $slot, $newstate);
00611     }
00612 
00613     public static function initialise_js() {
00614         global $CFG, $PAGE, $OUTPUT;
00615         static $done = false;
00616         if ($done) {
00617             return;
00618         }
00619         $module = array(
00620             'name' => 'core_question_flags',
00621             'fullpath' => '/question/flags.js',
00622             'requires' => array('base', 'dom', 'event-delegate', 'io-base'),
00623         );
00624         $actionurl = $CFG->wwwroot . '/question/toggleflag.php';
00625         $flagtext = array(
00626             0 => get_string('clickflag', 'question'),
00627             1 => get_string('clickunflag', 'question')
00628         );
00629         $flagattributes = array(
00630             0 => array(
00631                 'src' => $OUTPUT->pix_url('i/unflagged') . '',
00632                 'title' => get_string('clicktoflag', 'question'),
00633                 'alt' => get_string('notflagged', 'question'),
00634               //  'text' => get_string('clickflag', 'question'),
00635             ),
00636             1 => array(
00637                 'src' => $OUTPUT->pix_url('i/flagged') . '',
00638                 'title' => get_string('clicktounflag', 'question'),
00639                 'alt' => get_string('flagged', 'question'),
00640                // 'text' => get_string('clickunflag', 'question'),
00641             ),
00642         );
00643         $PAGE->requires->js_init_call('M.core_question_flags.init',
00644                 array($actionurl, $flagattributes, $flagtext), false, $module);
00645         $done = true;
00646     }
00647 }
00648 
00649 
00658 class question_out_of_sequence_exception extends moodle_exception {
00659     public function __construct($qubaid, $slot, $postdata) {
00660         if ($postdata == null) {
00661             $postdata = data_submitted();
00662         }
00663         parent::__construct('submissionoutofsequence', 'question', '', null,
00664                 "QUBAid: $qubaid, slot: $slot, post data: " . print_r($postdata, true));
00665     }
00666 }
00667 
00668 
00675 abstract class question_utils {
00685     public static function arrays_have_same_keys_and_values(array $array1, array $array2) {
00686         if (count($array1) != count($array2)) {
00687             return false;
00688         }
00689         foreach ($array1 as $key => $value1) {
00690             if (!array_key_exists($key, $array2)) {
00691                 return false;
00692             }
00693             if (((string) $value1) !== ((string) $array2[$key])) {
00694                 return false;
00695             }
00696         }
00697         return true;
00698     }
00699 
00712     public static function arrays_same_at_key(array $array1, array $array2, $key) {
00713         if (array_key_exists($key, $array1) && array_key_exists($key, $array2)) {
00714             return ((string) $array1[$key]) === ((string) $array2[$key]);
00715         }
00716         if (!array_key_exists($key, $array1) && !array_key_exists($key, $array2)) {
00717             return true;
00718         }
00719         return false;
00720     }
00721 
00732     public static function arrays_same_at_key_missing_is_blank(
00733             array $array1, array $array2, $key) {
00734         if (array_key_exists($key, $array1)) {
00735             $value1 = $array1[$key];
00736         } else {
00737             $value1 = '';
00738         }
00739         if (array_key_exists($key, $array2)) {
00740             $value2 = $array2[$key];
00741         } else {
00742             $value2 = '';
00743         }
00744         return ((string) $value1) === ((string) $value2);
00745     }
00746 
00757     public static function arrays_same_at_key_integer(
00758             array $array1, array $array2, $key) {
00759         if (array_key_exists($key, $array1)) {
00760             $value1 = $array1[$key];
00761         } else {
00762             $value1 = 0;
00763         }
00764         if (array_key_exists($key, $array2)) {
00765             $value2 = $array2[$key];
00766         } else {
00767             $value2 = 0;
00768         }
00769         return ((integer) $value1) === ((integer) $value2);
00770     }
00771 
00772     private static $units     = array('', 'i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix');
00773     private static $tens      = array('', 'x', 'xx', 'xxx', 'xl', 'l', 'lx', 'lxx', 'lxxx', 'xc');
00774     private static $hundreds  = array('', 'c', 'cc', 'ccc', 'cd', 'd', 'dc', 'dcc', 'dccc', 'cm');
00775     private static $thousands = array('', 'm', 'mm', 'mmm');
00776 
00783     public static function int_to_roman($number) {
00784         if (!is_integer($number) || $number < 1 || $number > 3999) {
00785             throw new coding_exception('Only integers between 0 and 3999 can be ' .
00786                     'converted to roman numerals.', $number);
00787         }
00788 
00789         return self::$thousands[$number / 1000 % 10] . self::$hundreds[$number / 100 % 10] .
00790                 self::$tens[$number / 10 % 10] . self::$units[$number % 10];
00791     }
00792 }
00793 
00794 
00801 interface question_variant_selection_strategy {
00808     public function choose_variant($maxvariants, $seed);
00809 }
00810 
00811 
00818 class question_variant_random_strategy implements question_variant_selection_strategy {
00819     public function choose_variant($maxvariants, $seed) {
00820         return rand(1, $maxvariants);
00821     }
00822 }
00823 
00824 
00834 class question_variant_pseudorandom_no_repeats_strategy
00835         implements question_variant_selection_strategy {
00836 
00838     protected $attemptno;
00839 
00841     protected $userid;
00842 
00848     public function __construct($attemptno, $userid = null) {
00849         $this->attemptno = $attemptno;
00850         if (is_null($userid)) {
00851             global $USER;
00852             $this->userid = $USER->id;
00853         } else {
00854             $this->userid = $userid;
00855         }
00856     }
00857 
00858     public function choose_variant($maxvariants, $seed) {
00859         if ($maxvariants == 1) {
00860             return 1;
00861         }
00862 
00863         $hash = sha1($seed . '|user' . $this->userid);
00864         $randint = hexdec(substr($hash, 17, 7));
00865 
00866         return ($randint + $this->attemptno) % $maxvariants + 1;
00867     }
00868 }
 All Data Structures Namespaces Files Functions Variables Enumerations