Merge branch 'MDL-67513-m310' of https://github.com/NeillM/moodle into MOODLE_310_STABLE
[moodle.git] / question / type / randomsamatch / question.php
CommitLineData
93a54393
JMV
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/>.
16
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 */
24
25
26defined('MOODLE_INTERNAL') || die();
27
28require_once($CFG->dirroot . '/question/type/match/question.php');
29
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 */
36class qtype_randomsamatch_question extends qtype_match_question {
37 /** @var qtype_randomsamatch_question_loader helper for loading the shortanswer questions. */
38 public $questionsloader;
39
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]);
48
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 }
55
56 // Save all the choices.
57 foreach ($this->choices as $key => $answer) {
58 $step->set_qt_var('_choice_' . $key, $answer);
59 }
60
61 parent::start_attempt($step, $variant);
62 }
63
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);
86
87 }
88
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 }
100}
101
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 */
108class 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;
113
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 }
123
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 }
132
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 }
140}