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 namespace qbank_previewquestion; 18 19 defined('MOODLE_INTERNAL') || die(); 20 21 require_once($CFG->dirroot . '/question/editlib.php'); 22 23 use action_menu; 24 use comment; 25 use context_module; 26 use context; 27 use core\plugininfo\qbank; 28 use core_question\local\bank\edit_menu_column; 29 use core_question\local\bank\view; 30 use core_question\local\bank\question_edit_contexts; 31 use moodle_url; 32 use question_bank; 33 use question_definition; 34 use question_display_options; 35 use question_engine; 36 use stdClass; 37 38 /** 39 * Class helper contains all the helper functions. 40 * 41 * @package qbank_previewquestion 42 * @copyright 2010 The Open University 43 * @author 2021 Safat Shahin <safatshahin@catalyst-au.net> 44 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 45 */ 46 class helper { 47 48 /** 49 * Called via pluginfile.php -> question_pluginfile to serve files belonging to 50 * a question in a question_attempt when that attempt is a preview. 51 * 52 * @param stdClass $course course settings object 53 * @param stdClass $context context object 54 * @param string $component the name of the component we are serving files for. 55 * @param string $filearea the name of the file area. 56 * @param int $qubaid the question_usage this image belongs to. 57 * @param int $slot the relevant slot within the usage. 58 * @param array $args the remaining bits of the file path. 59 * @param bool $forcedownload whether the user must be forced to download the file. 60 * @param array $fileoptions options for the stored files 61 * @return void false if file not found, does not return if found - justsend the file 62 */ 63 public static function question_preview_question_pluginfile($course, $context, $component, 64 $filearea, $qubaid, $slot, $args, $forcedownload, $fileoptions): void { 65 global $USER, $DB, $CFG; 66 67 list($context, $course, $cm) = get_context_info_array($context->id); 68 require_login($course, false, $cm); 69 70 $quba = question_engine::load_questions_usage_by_activity($qubaid); 71 72 if (!question_has_capability_on($quba->get_question($slot, false), 'use')) { 73 send_file_not_found(); 74 } 75 76 $options = new question_display_options(); 77 $options->feedback = question_display_options::VISIBLE; 78 $options->numpartscorrect = question_display_options::VISIBLE; 79 $options->generalfeedback = question_display_options::VISIBLE; 80 $options->rightanswer = question_display_options::VISIBLE; 81 $options->manualcomment = question_display_options::VISIBLE; 82 $options->history = question_display_options::VISIBLE; 83 if (!$quba->check_file_access($slot, $options, $component, 84 $filearea, $args, $forcedownload)) { 85 send_file_not_found(); 86 } 87 88 $fs = get_file_storage(); 89 $relativepath = implode('/', $args); 90 $fullpath = "/{$context->id}/{$component}/{$filearea}/{$relativepath}"; 91 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 92 send_file_not_found(); 93 } 94 95 send_stored_file($file, 0, 0, $forcedownload, $fileoptions); 96 } 97 98 /** 99 * The the URL to use for actions relating to this preview. 100 * 101 * @param int $questionid the question being previewed 102 * @param int $qubaid the id of the question usage for this preview 103 * @param question_preview_options $options the options in use 104 * @param context $context context for the question preview 105 * @param moodle_url $returnurl url of the page to return to 106 * @param int|null $restartversion version of the question to use when next restarting the preview. 107 * @return moodle_url 108 */ 109 public static function question_preview_action_url($questionid, $qubaid, 110 question_preview_options $options, $context, $returnurl = null, $restartversion = null): moodle_url { 111 $params = [ 112 'id' => $questionid, 113 'previewid' => $qubaid, 114 ]; 115 if ($context->contextlevel == CONTEXT_MODULE) { 116 $params['cmid'] = $context->instanceid; 117 } else if ($context->contextlevel == CONTEXT_COURSE) { 118 $params['courseid'] = $context->instanceid; 119 } 120 if ($returnurl !== null) { 121 $params['returnurl'] = $returnurl; 122 } 123 if ($restartversion !== null) { 124 $params['restartversion'] = $restartversion; 125 } 126 $params = array_merge($params, $options->get_url_params()); 127 return new moodle_url('/question/bank/previewquestion/preview.php', $params); 128 } 129 130 /** 131 * The the URL to use for actions relating to this preview. 132 * 133 * @param int $questionid the question being previewed 134 * @param context $context the current moodle context 135 * @param int $previewid optional previewid to sign post saved previewed answers 136 * @param moodle_url $returnurl url of the page to return to 137 * @return moodle_url 138 */ 139 public static function question_preview_form_url($questionid, $context, $previewid = null, $returnurl = null): moodle_url { 140 $params = [ 141 'id' => $questionid, 142 ]; 143 if ($context->contextlevel == CONTEXT_MODULE) { 144 $params['cmid'] = $context->instanceid; 145 } else if ($context->contextlevel == CONTEXT_COURSE) { 146 $params['courseid'] = $context->instanceid; 147 } 148 if ($previewid) { 149 $params['previewid'] = $previewid; 150 } 151 if ($returnurl !== null) { 152 $params['returnurl'] = $returnurl; 153 } 154 return new moodle_url('/question/bank/previewquestion/preview.php', $params); 155 } 156 157 /** 158 * Delete the current preview, if any, and redirect to start a new preview. 159 * 160 * @param int $previewid id of the preview while restarting it 161 * @param int $questionid id of the question in preview 162 * @param object $displayoptions display options for the question in preview 163 * @param object $context context of the question for preview 164 * @param moodle_url $returnurl url of the page to return to 165 * @param int|null $restartversion version of the question to use when next restarting the preview. 166 * @return void 167 */ 168 public static function restart_preview($previewid, $questionid, $displayoptions, $context, 169 $returnurl = null, $restartversion = null): void { 170 global $DB; 171 172 if ($previewid) { 173 $transaction = $DB->start_delegated_transaction(); 174 question_engine::delete_questions_usage_by_activity($previewid); 175 $transaction->allow_commit(); 176 } 177 redirect(self::question_preview_url($questionid, $displayoptions->behaviour, 178 $displayoptions->maxmark, $displayoptions, $displayoptions->variant, 179 $context, $returnurl, $restartversion)); 180 } 181 182 /** 183 * Generate the URL for starting a new preview of a given question with the given options. 184 * 185 * @param integer $questionid the question to preview 186 * @param string $preferredbehaviour the behaviour to use for the preview 187 * @param float $maxmark the maximum to mark the question out of 188 * @param question_display_options $displayoptions the display options to use 189 * @param int $variant the variant of the question to preview. If null, one will 190 * be picked randomly 191 * @param object $context context to run the preview in (affects things like 192 * filter settings, theme, lang, etc.) Defaults to $PAGE->context 193 * @param moodle_url $returnurl url of the page to return to 194 * @param int $restartversion The version of the question to use when restarting the preview. 195 * @return moodle_url the URL 196 */ 197 public static function question_preview_url($questionid, $preferredbehaviour = null, 198 $maxmark = null, $displayoptions = null, $variant = null, $context = null, $returnurl = null, 199 $restartversion = null): moodle_url { 200 201 $params = ['id' => $questionid]; 202 203 if (!is_null($restartversion)) { 204 $params['restartversion'] = $restartversion; 205 } 206 if (is_null($context)) { 207 global $PAGE; 208 $context = $PAGE->context; 209 } 210 if ($context->contextlevel == CONTEXT_MODULE) { 211 $params['cmid'] = $context->instanceid; 212 } else if ($context->contextlevel == CONTEXT_COURSE) { 213 $params['courseid'] = $context->instanceid; 214 } 215 216 if (!is_null($preferredbehaviour)) { 217 $params['behaviour'] = $preferredbehaviour; 218 } 219 220 if (!is_null($maxmark)) { 221 $params['maxmark'] = format_float($maxmark, -1); 222 } 223 224 if (!is_null($displayoptions)) { 225 $params['correctness'] = $displayoptions->correctness; 226 $params['marks'] = $displayoptions->marks; 227 $params['markdp'] = $displayoptions->markdp; 228 $params['feedback'] = (bool) $displayoptions->feedback; 229 $params['generalfeedback'] = (bool) $displayoptions->generalfeedback; 230 $params['rightanswer'] = (bool) $displayoptions->rightanswer; 231 $params['history'] = (bool) $displayoptions->history; 232 } 233 234 if (!is_null($returnurl)) { 235 $params['returnurl'] = $returnurl; 236 } 237 238 if ($variant) { 239 $params['variant'] = $variant; 240 } 241 242 return new moodle_url('/question/bank/previewquestion/preview.php', $params); 243 } 244 245 /** 246 * Popup params for the question preview. 247 * 248 * @return array that can be passed as $params to the {@see popup_action} constructor. 249 */ 250 public static function question_preview_popup_params(): array { 251 return [ 252 'height' => 600, 253 'width' => 800, 254 ]; 255 } 256 257 /** 258 * Get the extra elements for preview from qbank plugins. 259 * 260 * @param question_definition $question question definition object 261 * @param int $courseid id of the course 262 * @return array 263 */ 264 public static function get_preview_extra_elements(question_definition $question, int $courseid): array { 265 $plugins = get_plugin_list_with_function('qbank', 'preview_display'); 266 267 $comment = ''; 268 $extrahtml = []; 269 foreach ($plugins as $componentname => $plugin) { 270 $pluginhtml = component_callback($componentname, 'preview_display', [$question, $courseid]); 271 if ($componentname === 'qbank_comment') { 272 $comment = $pluginhtml; 273 continue; 274 } 275 $extrahtml[] = $pluginhtml; 276 } 277 return [$comment, $extrahtml]; 278 } 279 280 /** 281 * Checks if question is the latest version. 282 * 283 * @param string $version Question version to check 284 * @param string $questionbankentryid Entry to check against 285 * @return bool 286 */ 287 public static function is_latest(string $version, string $questionbankentryid) : bool { 288 global $DB; 289 290 $sql = 'SELECT MAX(version) AS max 291 FROM {question_versions} 292 WHERE questionbankentryid = ?'; 293 $latestversion = $DB->get_record_sql($sql, [$questionbankentryid]); 294 295 if (isset($latestversion->max)) { 296 return ($version === $latestversion->max) ? true : false; 297 } 298 return false; 299 } 300 301 /** 302 * Loads question version ids for current question. 303 * 304 * @param string $questionbankentryid Question bank entry id 305 * @return array $questionids Array containing question id as key and version as value. 306 */ 307 public static function load_versions(string $questionbankentryid) : array { 308 global $DB; 309 310 $questionids = []; 311 $sql = 'SELECT version, questionid 312 FROM {question_versions} 313 WHERE questionbankentryid = ? 314 ORDER BY version'; 315 316 $versions = $DB->get_records_sql($sql, [$questionbankentryid]); 317 foreach ($versions as $key => $version) { 318 $questionids[$version->questionid] = $key; 319 } 320 return $questionids; 321 } 322 323 /** 324 * Return the question ID from the array of id => version that corresponds to the requested version. 325 * 326 * If the requested version is question_preview_options::ALWAYS_LATEST, this will return the latest version. 327 * 328 * @param array $versions 329 * @param int $restartversion 330 * @return ?int 331 */ 332 public static function get_restart_id(array $versions, int $restartversion): ?int { 333 if ($restartversion === question_preview_options::ALWAYS_LATEST) { 334 return array_key_last($versions); 335 } else { 336 return array_search($restartversion, $versions) ?: null; 337 } 338 } 339 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body