Merge branch 'MDL-67513-m310' of https://github.com/NeillM/moodle into MOODLE_310_STABLE
[moodle.git] / question / type / randomsamatch / question.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Matching question definition class.
19  *
20  * @package   qtype_randomsamatch
21  * @copyright 2013 Jean-Michel Vedrine
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
26 defined('MOODLE_INTERNAL') || die();
28 require_once($CFG->dirroot . '/question/type/match/question.php');
30 /**
31  * Represents a randomsamatch question.
32  *
33  * @copyright 22013 Jean-Michel Vedrine
34  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class qtype_randomsamatch_question extends qtype_match_question {
37     /** @var qtype_randomsamatch_question_loader helper for loading the shortanswer questions. */
38     public $questionsloader;
40     public function start_attempt(question_attempt_step $step, $variant) {
41         $saquestions = $this->questionsloader->load_questions();
42         foreach ($saquestions as $wrappedquestion) {
43             // Store and save stem text and format.
44             $this->stems[$wrappedquestion->id] = $wrappedquestion->questiontext;
45             $this->stemformat[$wrappedquestion->id] = $wrappedquestion->questiontextformat;
46             $step->set_qt_var('_stem_' . $wrappedquestion->id, $this->stems[$wrappedquestion->id]);
47             $step->set_qt_var('_stemformat_' . $wrappedquestion->id, $this->stemformat[$wrappedquestion->id]);
49             // Find, store and save right choice id.
50             $key = $this->find_right_answer($wrappedquestion);
51             $this->right[$wrappedquestion->id] = $key;
52             $step->set_qt_var('_right_' . $wrappedquestion->id, $key);
53             // No need to save saquestions, it will be saved by parent class in _stemorder.
54         }
56         // Save all the choices.
57         foreach ($this->choices as $key => $answer) {
58             $step->set_qt_var('_choice_' . $key, $answer);
59         }
61         parent::start_attempt($step, $variant);
62     }
64     /**
65      * Find the corresponding choice id of the first correct answer of a shortanswer question.
66      * choice is added to the randomsamatch question if it doesn't already exist.
67      * @param object $wrappedquestion short answer question.
68      * @return int correct choice id.
69      */
70     public function find_right_answer($wrappedquestion) {
71         // We only take into account *one* (the first) correct answer.
72         while ($answer = array_shift($wrappedquestion->answers)) {
73             if (!question_state::graded_state_for_fraction(
74                     $answer->fraction)->is_incorrect()) {
75                 // Store this answer as a choice, only if this is a new one.
76                 $key = array_search($answer->answer, $this->choices);
77                 if ($key === false) {
78                     $key = $answer->id;
79                     $this->choices[$key] = $answer->answer;
80                 }
81                 return $key;
82             }
83         }
84         // We should never get there.
85         throw new coding_exception('shortanswerquestionwithoutrightanswer', $wrappedquestion->id);
87     }
89     public function apply_attempt_state(question_attempt_step $step) {
90         $saquestions = explode(',', $step->get_qt_var('_stemorder'));
91         foreach ($saquestions as $questionid) {
92             $this->stems[$questionid] = $step->get_qt_var('_stem_' . $questionid);
93             $this->stemformat[$questionid] = $step->get_qt_var('_stemformat_' . $questionid);
94             $key = $step->get_qt_var('_right_' . $questionid);
95             $this->right[$questionid] = $key;
96             $this->choices[$key] = $step->get_qt_var('_choice_' . $key);
97         }
98         parent::apply_attempt_state($step);
99     }
102 /**
103  * This class is responsible for loading the questions that a question needs from the database.
104  *
105  * @copyright  2013 Jean-Michel vedrine
106  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
107  */
108 class qtype_randomsamatch_question_loader {
109     /** @var array hold available shortanswers questionid to choose from. */
110     protected $availablequestions;
111     /** @var int how many questions to load. */
112     protected $choose;
114     /**
115      * Constructor
116      * @param array $availablequestions array of available question ids.
117      * @param int $choose how many questions to load.
118      */
119     public function __construct($availablequestions, $choose) {
120         $this->availablequestions = $availablequestions;
121         $this->choose = $choose;
122     }
124     /**
125      * Choose and load the desired number of questions.
126      * @return array of short answer questions.
127      */
128     public function load_questions() {
129         if ($this->choose > count($this->availablequestions)) {
130             throw new coding_exception('notenoughtshortanswerquestions');
131         }
133         $questionids = draw_rand_array($this->availablequestions, $this->choose);
134         $questions = array();
135         foreach ($questionids as $questionid) {
136             $questions[] = question_bank::load_question($questionid);
137         }
138         return $questions;
139     }