See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]
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 * Quiz module test data generator. 19 * 20 * @package core_question 21 * @copyright 2013 The Open University 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 use core_question\local\bank\question_version_status; 26 27 /** 28 * Class core_question_generator for generating question data. 29 * 30 * @package core_question 31 * @copyright 2013 The Open University 32 * @author 2021 Safat Shahin <safatshahin@catalyst-au.net> 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class core_question_generator extends component_generator_base { 36 37 /** 38 * @var number of created instances 39 */ 40 protected $categorycount = 0; 41 42 /** 43 * Make the category count to zero. 44 */ 45 public function reset() { 46 $this->categorycount = 0; 47 } 48 49 /** 50 * Create a new question category. 51 * 52 * @param array|stdClass $record 53 * @return stdClass question_categories record. 54 */ 55 public function create_question_category($record = null) { 56 global $DB; 57 58 $this->categorycount ++; 59 60 $defaults = [ 61 'name' => 'Test question category ' . $this->categorycount, 62 'info' => '', 63 'infoformat' => FORMAT_HTML, 64 'stamp' => make_unique_id_code(), 65 'sortorder' => 999, 66 'idnumber' => null 67 ]; 68 69 $record = $this->datagenerator->combine_defaults_and_record($defaults, $record); 70 71 if (!isset($record['contextid'])) { 72 $record['contextid'] = context_system::instance()->id; 73 } 74 if (!isset($record['parent'])) { 75 $record['parent'] = question_get_top_category($record['contextid'], true)->id; 76 } 77 $record['id'] = $DB->insert_record('question_categories', $record); 78 return (object) $record; 79 } 80 81 /** 82 * Create a new question. The question is initialised using one of the 83 * examples from the appropriate {@link question_test_helper} subclass. 84 * Then, any files you want to change from the value in the base example you 85 * can override using $overrides. 86 * 87 * @param string $qtype the question type to create an example of. 88 * @param string $which as for the corresponding argument of 89 * {@link question_test_helper::get_question_form_data}. null for the default one. 90 * @param array|stdClass $overrides any fields that should be different from the base example. 91 * @return stdClass the question data. 92 */ 93 public function create_question($qtype, $which = null, $overrides = null) { 94 $question = new stdClass(); 95 $question->qtype = $qtype; 96 $question->createdby = 0; 97 $question->idnumber = null; 98 $question->status = question_version_status::QUESTION_STATUS_READY; 99 100 return $this->update_question($question, $which, $overrides); 101 } 102 103 /** 104 * Create a tag on a question. 105 * 106 * @param array $data with two elements ['questionid' => 123, 'tag' => 'mytag']. 107 */ 108 public function create_question_tag(array $data): void { 109 $question = question_bank::load_question($data['questionid']); 110 core_tag_tag::add_item_tag('core_question', 'question', $question->id, 111 context::instance_by_id($question->contextid), $data['tag'], 0); 112 } 113 114 /** 115 * Update an existing question. 116 * 117 * @param stdClass $question the question data to update. 118 * @param string $which as for the corresponding argument of 119 * {@link question_test_helper::get_question_form_data}. null for the default one. 120 * @param array|stdClass $overrides any fields that should be different from the base example. 121 * @return stdClass the question data, including version info and questionbankentryid 122 */ 123 public function update_question($question, $which = null, $overrides = null) { 124 global $CFG, $DB; 125 require_once($CFG->dirroot . '/question/engine/tests/helpers.php'); 126 $question = clone($question); 127 128 $qtype = $question->qtype; 129 130 $fromform = test_question_maker::get_question_form_data($qtype, $which); 131 $fromform = (object) $this->datagenerator->combine_defaults_and_record((array) $question, $fromform); 132 $fromform = (object) $this->datagenerator->combine_defaults_and_record((array) $fromform, $overrides); 133 $fromform->status = $fromform->status ?? $question->status; 134 135 $question = question_bank::get_qtype($qtype)->save_question($question, $fromform); 136 137 if ($overrides && (array_key_exists('createdby', $overrides) || array_key_exists('modifiedby', $overrides))) { 138 // Manually update the createdby and modifiedby because questiontypebase forces 139 // current user and some tests require a specific user. 140 if (array_key_exists('createdby', $overrides)) { 141 $question->createdby = $overrides['createdby']; 142 } 143 if (array_key_exists('modifiedby', $overrides)) { 144 $question->modifiedby = $overrides['modifiedby']; 145 } 146 $DB->update_record('question', $question); 147 } 148 $questionversion = $DB->get_record('question_versions', ['questionid' => $question->id], '*', MUST_EXIST); 149 $question->versionid = $questionversion->id; 150 $question->questionbankentryid = $questionversion->questionbankentryid; 151 $question->version = $questionversion->version; 152 $question->status = $questionversion->status; 153 154 return $question; 155 } 156 157 /** 158 * Setup a course category, course, a question category, and 2 questions 159 * for testing. 160 * 161 * @param string $type The type of question category to create. 162 * @return array The created data objects 163 */ 164 public function setup_course_and_questions($type = 'course') { 165 $datagenerator = $this->datagenerator; 166 $category = $datagenerator->create_category(); 167 $course = $datagenerator->create_course([ 168 'numsections' => 5, 169 'category' => $category->id 170 ]); 171 172 switch ($type) { 173 case 'category': 174 $context = context_coursecat::instance($category->id); 175 break; 176 177 case 'system': 178 $context = context_system::instance(); 179 break; 180 181 default: 182 $context = context_course::instance($course->id); 183 break; 184 } 185 186 $qcat = $this->create_question_category(['contextid' => $context->id]); 187 188 $questions = [ 189 $this->create_question('shortanswer', null, ['category' => $qcat->id]), 190 $this->create_question('shortanswer', null, ['category' => $qcat->id]), 191 ]; 192 193 return [$category, $course, $qcat, $questions]; 194 } 195 196 /** 197 * This method can construct what the post data would be to simulate a user submitting 198 * responses to a number of questions within a question usage. 199 * 200 * In the responses array, the array keys are the slot numbers for which a response will 201 * be submitted. You can submit a response to any number of responses within the usage. 202 * There is no need to do them all. The values are a string representation of the response. 203 * The exact meaning of that depends on the particular question type. These strings 204 * are passed to the un_summarise_response method of the question to decode. 205 * 206 * @param question_usage_by_activity $quba the question usage. 207 * @param array $responses the resonses to submit, in the format described above. 208 * @param bool $checkbutton if simulate a click on the check button for each question, else simulate save. 209 * This should only be used with behaviours that have a check button. 210 * @return array that can be passed to methods like $quba->process_all_actions as simulated POST data. 211 */ 212 public function get_simulated_post_data_for_questions_in_usage( 213 question_usage_by_activity $quba, array $responses, $checkbutton) { 214 $postdata = []; 215 216 foreach ($responses as $slot => $responsesummary) { 217 $postdata += $this->get_simulated_post_data_for_question_attempt( 218 $quba->get_question_attempt($slot), $responsesummary, $checkbutton); 219 } 220 221 return $postdata; 222 } 223 224 /** 225 * This method can construct what the post data would be to simulate a user submitting 226 * responses to one particular question attempt. 227 * 228 * The $responsesummary is a string representation of the response to be submitted. 229 * The exact meaning of that depends on the particular question type. These strings 230 * are passed to the un_summarise_response method of the question to decode. 231 * 232 * @param question_attempt $qa the question attempt for which we are generating POST data. 233 * @param string $responsesummary a textual summary of the response, as described above. 234 * @param bool $checkbutton if simulate a click on the check button, else simulate save. 235 * This should only be used with behaviours that have a check button. 236 * @return array the simulated post data that can be passed to $quba->process_all_actions. 237 */ 238 public function get_simulated_post_data_for_question_attempt( 239 question_attempt $qa, $responsesummary, $checkbutton) { 240 241 $question = $qa->get_question(); 242 if (!$question instanceof question_with_responses) { 243 return []; 244 } 245 246 $postdata = []; 247 $postdata[$qa->get_control_field_name('sequencecheck')] = (string)$qa->get_sequence_check_count(); 248 $postdata[$qa->get_flag_field_name()] = (string)(int)$qa->is_flagged(); 249 250 $response = $question->un_summarise_response($responsesummary); 251 foreach ($response as $name => $value) { 252 $postdata[$qa->get_qt_field_name($name)] = (string)$value; 253 } 254 255 // TODO handle behaviour variables better than this. 256 if ($checkbutton) { 257 $postdata[$qa->get_behaviour_field_name('submit')] = 1; 258 } 259 260 return $postdata; 261 } 262 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body