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