on-demand release 2.1beta
[moodle.git] / question / type / random / questiontype.php
CommitLineData
aeb15530 1<?php
f9b0500f
TH
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
1976496e 17/**
f9b0500f 18 * Question type class for the random question type.
f34488b2 19 *
7764183a
TH
20 * @package qtype
21 * @subpackage random
22 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
f9b0500f
TH
24 */
25
26
a17b297d
TH
27defined('MOODLE_INTERNAL') || die();
28
29
f9b0500f
TH
30/**
31 * The random question type.
32 *
33 * This question type does not have a question definition class, nor any
34 * renderers. When you load a question of this type, it actually loads a
35 * question chosen randomly from a particular category in the question bank.
f34488b2 36 *
7764183a
TH
37 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
53a4d39f 39 */
f9b0500f
TH
40class qtype_random extends question_type {
41 /** @var string comma-separated list of qytpe names not to select, can be used in SQL. */
f24493ec 42 protected $excludedqtypes = null;
516cf3eb 43
f9b0500f
TH
44 /** @var string comma-separated list of manually graded qytpe names, can be used in SQL. */
45 protected $manualqtypes = null;
516cf3eb 46
f9b0500f
TH
47 /**
48 * Cache of availabe question ids from a particular category.
49 * @var array two-dimensional array. The first key is a category id, the
50 * second key is wether subcategories should be included.
51 */
52 private $availablequestionsbycategory = array();
516cf3eb 53
f9b0500f 54 public function menu_name() {
8b192edb 55 // Don't include this question type in the 'add new question' menu.
a2156789 56 return false;
57 }
58
f9b0500f 59 public function is_manual_graded() {
f24493ec 60 return true;
61 }
62
f9b0500f
TH
63 public function is_usable_by_random() {
64 return false;
f24493ec 65 }
66
f9b0500f 67 public function is_question_manual_graded($question, $otherquestionsinuse) {
f24493ec 68 global $DB;
69 // We take our best shot at working whether a particular question is manually
70 // graded follows: We look to see if any of the questions that this random
71 // question might select if of a manually graded type. If a category contains
72 // a mixture of manual and non-manual questions, and if all the attempts so
73 // far selected non-manual ones, this will give the wrong answer, but we
74 // don't care. Even so, this is an expensive calculation!
75 $this->init_qtype_lists();
76 if (!$this->manualqtypes) {
77 return false;
78 }
79 if ($question->questiontext) {
80 $categorylist = question_categorylist($question->category);
81 } else {
2daffca5 82 $categorylist = array($question->category);
f24493ec 83 }
2daffca5
TH
84 list($qcsql, $qcparams) = $DB->get_in_or_equal($categorylist);
85 // TODO use in_or_equal for $otherquestionsinuse and $this->manualqtypes
f24493ec 86 return $DB->record_exists_select('question',
2daffca5 87 "category $qcsql
f24493ec 88 AND parent = 0
89 AND hidden = 0
90 AND id NOT IN ($otherquestionsinuse)
2daffca5 91 AND qtype IN ($this->manualqtypes)", $qcparams);
f24493ec 92 }
93
f24493ec 94 /**
95 * This method needs to be called before the ->excludedqtypes and
96 * ->manualqtypes fields can be used.
97 */
f9b0500f
TH
98 protected function init_qtype_lists() {
99 if (!is_null($this->excludedqtypes)) {
100 return; // Already done.
101 }
102 $excludedqtypes = array();
103 $manualqtypes = array();
104 foreach (question_bank::get_all_qtypes() as $qtype) {
105 $quotedname = "'" . $qtype->name() . "'";
106 if (!$qtype->is_usable_by_random()) {
107 $excludedqtypes[] = $quotedname;
108 } else if ($qtype->is_manual_graded()) {
109 $manualqtypes[] = $quotedname;
f24493ec 110 }
f24493ec 111 }
f9b0500f
TH
112 $this->excludedqtypes = implode(',', $excludedqtypes);
113 $this->manualqtypes = implode(',', $manualqtypes);
f24493ec 114 }
115
59f26004 116 public function display_question_editing_page(&$mform, $question, $wizardnow) {
fe6ce234 117 global $OUTPUT;
4995b9c1 118 $heading = $this->get_heading(empty($question->id));
4bcc5118 119 echo $OUTPUT->heading_with_help($heading, $this->name(), $this->plugin_name());
bcc234b0 120 $mform->display();
121 }
122
f9b0500f 123 public function get_question_options($question) {
516cf3eb 124 return true;
125 }
126
f59dba84 127 /**
128 * Random questions always get a question name that is Random (cateogryname).
129 * This function is a centralised place to calculate that, given the category.
59f26004 130 * @param object $category the category this question picks from. (Only ->name is used.)
f7970e3c 131 * @param bool $includesubcategories whether this question also picks from subcategories.
3cac440b 132 * @return string the name this question should have.
f59dba84 133 */
f9b0500f 134 public function question_name($category, $includesubcategories) {
3cac440b 135 if ($includesubcategories) {
136 $string = 'randomqplusname';
137 } else {
138 $string = 'randomqname';
139 }
140 return get_string($string, 'qtype_random', $category->name);
f59dba84 141 }
142
f9b0500f 143 protected function set_selected_question_name($question, $randomname) {
0ff4bd08 144 $a = new stdClass();
f9b0500f
TH
145 $a->randomname = $randomname;
146 $a->questionname = $question->name;
147 $question->name = get_string('selectedby', 'qtype_random', $a);
5348b899 148 }
149
a13d4fbd 150 public function save_question($question, $form) {
f9b0500f
TH
151 $form->name = '';
152 // Name is not a required field for random questions, but
153 // parent::save_question Assumes that it is.
a13d4fbd 154 return parent::save_question($question, $form);
f9b0500f 155 }
24e8b9b6 156
f9b0500f 157 public function save_question_options($question) {
a13d4fbd
TH
158 global $DB;
159
24e8b9b6 160 // No options, as such, but we set the parent field to the question's
161 // own id. Setting the parent field has the effect of hiding this
162 // question in various places.
0ff4bd08 163 $updateobject = new stdClass();
24e8b9b6 164 $updateobject->id = $question->id;
165 $updateobject->parent = $question->id;
166
167 // We also force the question name to be 'Random (categoryname)'.
59f26004
TH
168 $category = $DB->get_record('question_categories',
169 array('id' => $question->category), '*', MUST_EXIST);
3cac440b 170 $updateobject->name = $this->question_name($category, !empty($question->questiontext));
24e8b9b6 171 return $DB->update_record('question', $updateobject);
516cf3eb 172 }
173
339ef4c2 174 /**
175 * Get all the usable questions from a particular question category.
176 *
f7970e3c
TH
177 * @param int $categoryid the id of a question category.
178 * @param bool whether to include questions from subcategories.
59f26004
TH
179 * @param string $questionsinuse comma-separated list of question ids to
180 * exclude from consideration.
339ef4c2 181 * @return array of question records.
182 */
f9b0500f
TH
183 public function get_available_questions_from_category($categoryid, $subcategories) {
184 if (isset($this->availablequestionsbycategory[$categoryid][$subcategories])) {
185 return $this->availablequestionsbycategory[$categoryid][$subcategories];
186 }
187
f24493ec 188 $this->init_qtype_lists();
339ef4c2 189 if ($subcategories) {
f9b0500f 190 $categoryids = question_categorylist($categoryid);
339ef4c2 191 } else {
2daffca5 192 $categoryids = array($categoryid);
516cf3eb 193 }
194
f9b0500f
TH
195 $questionids = question_bank::get_finder()->get_questions_from_categories(
196 $categoryids, 'qtype NOT IN (' . $this->excludedqtypes . ')');
197 $this->availablequestionsbycategory[$categoryid][$subcategories] = $questionids;
198 return $questionids;
516cf3eb 199 }
200
f9b0500f
TH
201 public function make_question($questiondata) {
202 return $this->choose_other_question($questiondata, array());
516cf3eb 203 }
204
f9b0500f
TH
205 /**
206 * Load the definition of another question picked randomly by this question.
207 * @param object $questiondata the data defining a random question.
59f26004
TH
208 * @param array $excludedquestions of question ids. We will no pick any
209 * question whose id is in this list.
210 * @param bool $allowshuffle if false, then any shuffle option on the
211 * selected quetsion is disabled.
f9b0500f
TH
212 * @return question_definition|null the definition of the question that was
213 * selected, or null if no suitable question could be found.
214 */
215 public function choose_other_question($questiondata, $excludedquestions, $allowshuffle = true) {
216 $available = $this->get_available_questions_from_category($questiondata->category,
217 !empty($questiondata->questiontext));
218 shuffle($available);
219
220 foreach ($available as $questionid) {
221 if (in_array($questionid, $excludedquestions)) {
222 continue;
223 }
516cf3eb 224
f9b0500f
TH
225 $question = question_bank::load_question($questionid, $allowshuffle);
226 $this->set_selected_question_name($question, $questiondata->name);
227 return $question;
516cf3eb 228 }
f9b0500f 229 return null;
516cf3eb 230 }
231
c7df5006 232 public function get_random_guess_score($questiondata) {
f9b0500f 233 return null;
516cf3eb 234 }
516cf3eb 235}