first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,3 @@
define("qbank_previewquestion/preview",["exports","core_question/question_engine"],(function(_exports,_question_engine){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;_exports.init=(redirect,url)=>{if(!redirect){document.getElementById("close-previewquestion-page").addEventListener("click",(e=>{e.preventDefault(),null===window.opener?location.href=url:window.close()}))}(0,_question_engine.initForm)("#responseform")}}));
//# sourceMappingURL=preview.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"preview.min.js","sources":["../src/preview.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Javascript for preview.\n *\n * @module qbank_previewquestion/preview\n * @copyright 2021 Catalyst IT Australia Pty Ltd\n * @author Safat Shahin <safatshahin@catalyst-au.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {initForm as initQuestionEngineForm} from 'core_question/question_engine';\n\n/**\n * Set up the actions.\n *\n * @method init\n * @param {bool} redirect Redirect.\n * @param {string} url url to redirect.\n */\nexport const init = (redirect, url) => {\n if (!redirect) {\n const closeButton = document.getElementById('close-previewquestion-page');\n closeButton.addEventListener('click', (e) => {\n e.preventDefault();\n if (window.opener === null) {\n location.href = url;\n } else {\n window.close();\n }\n });\n }\n // Set up the form to be displayed.\n initQuestionEngineForm('#responseform');\n};\n"],"names":["redirect","url","document","getElementById","addEventListener","e","preventDefault","window","opener","location","href","close"],"mappings":"mNAiCoB,CAACA,SAAUC,WACtBD,SAAU,CACSE,SAASC,eAAe,8BAChCC,iBAAiB,SAAUC,IACnCA,EAAEC,iBACoB,OAAlBC,OAAOC,OACPC,SAASC,KAAOT,IAEhBM,OAAOI,yCAKI"}
@@ -0,0 +1,48 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Javascript for preview.
*
* @module qbank_previewquestion/preview
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {initForm as initQuestionEngineForm} from 'core_question/question_engine';
/**
* Set up the actions.
*
* @method init
* @param {bool} redirect Redirect.
* @param {string} url url to redirect.
*/
export const init = (redirect, url) => {
if (!redirect) {
const closeButton = document.getElementById('close-previewquestion-page');
closeButton.addEventListener('click', (e) => {
e.preventDefault();
if (window.opener === null) {
location.href = url;
} else {
window.close();
}
});
}
// Set up the form to be displayed.
initQuestionEngineForm('#responseform');
};
@@ -0,0 +1,106 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qbank_previewquestion\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
use moodleform;
use question_display_options;
use question_engine;
use qbank_previewquestion\question_preview_options;
/**
* Settings form for the preview options.
*
* @package qbank_previewquestion
* @copyright 2009 The Open University
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class preview_options_form extends moodleform {
public function definition() {
$mform = $this->_form;
$hiddenorvisible = [
question_display_options::HIDDEN => get_string('notshown', 'question'),
question_display_options::VISIBLE => get_string('shown', 'question'),
];
$mform->addElement('header', 'attemptoptionsheader', get_string('previewoptions', 'qbank_previewquestion'));
$mform->setExpanded('attemptoptionsheader', false);
// Add html element with class to display long text in single line.
$mform->addElement('html', \html_writer::div(get_string('theoptionsyouselectonlyaffectthepreview',
'qbank_previewquestion'), "col-md-12 row d-flex col-form-label mb-3"));
$versions = $this->_customdata['versions'];
$versions[question_preview_options::ALWAYS_LATEST] = get_string('alwayslatest', 'qbank_previewquestion');
$currentversion = $this->_customdata['restartversion'];
$select = $mform->addElement('select', 'restartversion', get_string('questionversion', 'qbank_previewquestion'), $versions);
$select->setSelected($currentversion);
$behaviours = question_engine::get_behaviour_options(
$this->_customdata['quba']->get_preferred_behaviour());
$mform->addElement('select', 'behaviour',
get_string('howquestionsbehave', 'question'), $behaviours);
$mform->addHelpButton('behaviour', 'howquestionsbehave', 'question');
$mform->addElement('float', 'maxmark', get_string('markedoutof', 'question'), ['size' => '5']);
if ($this->_customdata['maxvariant'] > 1) {
$variants = range(1, $this->_customdata['maxvariant']);
$mform->addElement('select', 'variant', get_string('questionvariant', 'question'),
array_combine($variants, $variants));
}
$mform->setType('variant', PARAM_INT);
$mform->addElement('submit', 'saverestart',
get_string('restartwiththeseoptions', 'question'));
$mform->addElement('header', 'displayoptionsheader', get_string('displayoptions', 'question'));
$mform->setExpanded('displayoptionsheader', false);
$mform->addElement('select', 'correctness', get_string('whethercorrect', 'question'),
$hiddenorvisible);
$marksoptions = [
question_display_options::HIDDEN => get_string('notshown', 'question'),
question_display_options::MAX_ONLY => get_string('showmaxmarkonly', 'question'),
question_display_options::MARK_AND_MAX => get_string('showmarkandmax', 'question'),
];
$mform->addElement('select', 'marks', get_string('marks', 'question'), $marksoptions);
$mform->addElement('select', 'markdp', get_string('decimalplacesingrades', 'question'),
question_engine::get_dp_options());
$mform->addElement('select', 'feedback',
get_string('specificfeedback', 'question'), $hiddenorvisible);
$mform->addElement('select', 'generalfeedback',
get_string('generalfeedback', 'question'), $hiddenorvisible);
$mform->addElement('select', 'rightanswer',
get_string('rightanswer', 'question'), $hiddenorvisible);
$mform->addElement('select', 'history',
get_string('responsehistory', 'question'), $hiddenorvisible);
$mform->addElement('submit', 'saveupdate',
get_string('updatedisplayoptions', 'question'));
}
}
@@ -0,0 +1,339 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qbank_previewquestion;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/editlib.php');
use action_menu;
use comment;
use context_module;
use context;
use core\plugininfo\qbank;
use core_question\local\bank\edit_menu_column;
use core_question\local\bank\view;
use core_question\local\bank\question_edit_contexts;
use moodle_url;
use question_bank;
use question_definition;
use question_display_options;
use question_engine;
use stdClass;
/**
* Class helper contains all the helper functions.
*
* @package qbank_previewquestion
* @copyright 2010 The Open University
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Called via pluginfile.php -> question_pluginfile to serve files belonging to
* a question in a question_attempt when that attempt is a preview.
*
* @param stdClass $course course settings object
* @param stdClass $context context object
* @param string $component the name of the component we are serving files for.
* @param string $filearea the name of the file area.
* @param int $qubaid the question_usage this image belongs to.
* @param int $slot the relevant slot within the usage.
* @param array $args the remaining bits of the file path.
* @param bool $forcedownload whether the user must be forced to download the file.
* @param array $fileoptions options for the stored files
* @return void false if file not found, does not return if found - justsend the file
*/
public static function question_preview_question_pluginfile($course, $context, $component,
$filearea, $qubaid, $slot, $args, $forcedownload, $fileoptions): void {
global $USER, $DB, $CFG;
list($context, $course, $cm) = get_context_info_array($context->id);
require_login($course, false, $cm);
$quba = question_engine::load_questions_usage_by_activity($qubaid);
if (!question_has_capability_on($quba->get_question($slot, false), 'use')) {
send_file_not_found();
}
$options = new question_display_options();
$options->feedback = question_display_options::VISIBLE;
$options->numpartscorrect = question_display_options::VISIBLE;
$options->generalfeedback = question_display_options::VISIBLE;
$options->rightanswer = question_display_options::VISIBLE;
$options->manualcomment = question_display_options::VISIBLE;
$options->history = question_display_options::VISIBLE;
if (!$quba->check_file_access($slot, $options, $component,
$filearea, $args, $forcedownload)) {
send_file_not_found();
}
$fs = get_file_storage();
$relativepath = implode('/', $args);
$fullpath = "/{$context->id}/{$component}/{$filearea}/{$relativepath}";
if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
send_file_not_found();
}
send_stored_file($file, 0, 0, $forcedownload, $fileoptions);
}
/**
* The the URL to use for actions relating to this preview.
*
* @param int $questionid the question being previewed
* @param int $qubaid the id of the question usage for this preview
* @param question_preview_options $options the options in use
* @param context $context context for the question preview
* @param moodle_url $returnurl url of the page to return to
* @param int|null $restartversion version of the question to use when next restarting the preview.
* @return moodle_url
*/
public static function question_preview_action_url($questionid, $qubaid,
question_preview_options $options, $context, $returnurl = null, $restartversion = null): moodle_url {
$params = [
'id' => $questionid,
'previewid' => $qubaid,
];
if ($context->contextlevel == CONTEXT_MODULE) {
$params['cmid'] = $context->instanceid;
} else if ($context->contextlevel == CONTEXT_COURSE) {
$params['courseid'] = $context->instanceid;
}
if ($returnurl !== null) {
$params['returnurl'] = $returnurl;
}
if ($restartversion !== null) {
$params['restartversion'] = $restartversion;
}
$params = array_merge($params, $options->get_url_params());
return new moodle_url('/question/bank/previewquestion/preview.php', $params);
}
/**
* The the URL to use for actions relating to this preview.
*
* @param int $questionid the question being previewed
* @param context $context the current moodle context
* @param int $previewid optional previewid to sign post saved previewed answers
* @param moodle_url $returnurl url of the page to return to
* @return moodle_url
*/
public static function question_preview_form_url($questionid, $context, $previewid = null, $returnurl = null): moodle_url {
$params = [
'id' => $questionid,
];
if ($context->contextlevel == CONTEXT_MODULE) {
$params['cmid'] = $context->instanceid;
} else if ($context->contextlevel == CONTEXT_COURSE) {
$params['courseid'] = $context->instanceid;
}
if ($previewid) {
$params['previewid'] = $previewid;
}
if ($returnurl !== null) {
$params['returnurl'] = $returnurl;
}
return new moodle_url('/question/bank/previewquestion/preview.php', $params);
}
/**
* Delete the current preview, if any, and redirect to start a new preview.
*
* @param int $previewid id of the preview while restarting it
* @param int $questionid id of the question in preview
* @param object $displayoptions display options for the question in preview
* @param object $context context of the question for preview
* @param moodle_url $returnurl url of the page to return to
* @param int|null $restartversion version of the question to use when next restarting the preview.
* @return void
*/
public static function restart_preview($previewid, $questionid, $displayoptions, $context,
$returnurl = null, $restartversion = null): void {
global $DB;
if ($previewid) {
$transaction = $DB->start_delegated_transaction();
question_engine::delete_questions_usage_by_activity($previewid);
$transaction->allow_commit();
}
redirect(self::question_preview_url($questionid, $displayoptions->behaviour,
$displayoptions->maxmark, $displayoptions, $displayoptions->variant,
$context, $returnurl, $restartversion));
}
/**
* Generate the URL for starting a new preview of a given question with the given options.
*
* @param integer $questionid the question to preview
* @param string $preferredbehaviour the behaviour to use for the preview
* @param float $maxmark the maximum to mark the question out of
* @param question_display_options $displayoptions the display options to use
* @param int $variant the variant of the question to preview. If null, one will
* be picked randomly
* @param object $context context to run the preview in (affects things like
* filter settings, theme, lang, etc.) Defaults to $PAGE->context
* @param moodle_url $returnurl url of the page to return to
* @param int $restartversion The version of the question to use when restarting the preview.
* @return moodle_url the URL
*/
public static function question_preview_url($questionid, $preferredbehaviour = null,
$maxmark = null, $displayoptions = null, $variant = null, $context = null, $returnurl = null,
$restartversion = null): moodle_url {
$params = ['id' => $questionid];
if (!is_null($restartversion)) {
$params['restartversion'] = $restartversion;
}
if (is_null($context)) {
global $PAGE;
$context = $PAGE->context;
}
if ($context->contextlevel == CONTEXT_MODULE) {
$params['cmid'] = $context->instanceid;
} else if ($context->contextlevel == CONTEXT_COURSE) {
$params['courseid'] = $context->instanceid;
}
if (!is_null($preferredbehaviour)) {
$params['behaviour'] = $preferredbehaviour;
}
if (!is_null($maxmark)) {
$params['maxmark'] = format_float($maxmark, -1);
}
if (!is_null($displayoptions)) {
$params['correctness'] = $displayoptions->correctness;
$params['marks'] = $displayoptions->marks;
$params['markdp'] = $displayoptions->markdp;
$params['feedback'] = (bool) $displayoptions->feedback;
$params['generalfeedback'] = (bool) $displayoptions->generalfeedback;
$params['rightanswer'] = (bool) $displayoptions->rightanswer;
$params['history'] = (bool) $displayoptions->history;
}
if (!is_null($returnurl)) {
$params['returnurl'] = $returnurl;
}
if ($variant) {
$params['variant'] = $variant;
}
return new moodle_url('/question/bank/previewquestion/preview.php', $params);
}
/**
* Popup params for the question preview.
*
* @return array that can be passed as $params to the {@see popup_action} constructor.
*/
public static function question_preview_popup_params(): array {
return [
'height' => 600,
'width' => 800,
];
}
/**
* Get the extra elements for preview from qbank plugins.
*
* @param question_definition $question question definition object
* @param int $courseid id of the course
* @return array
*/
public static function get_preview_extra_elements(question_definition $question, int $courseid): array {
$plugins = get_plugin_list_with_function('qbank', 'preview_display');
$comment = '';
$extrahtml = [];
foreach ($plugins as $componentname => $plugin) {
$pluginhtml = component_callback($componentname, 'preview_display', [$question, $courseid]);
if ($componentname === 'qbank_comment') {
$comment = $pluginhtml;
continue;
}
$extrahtml[] = $pluginhtml;
}
return [$comment, $extrahtml];
}
/**
* Checks if question is the latest version.
*
* @param string $version Question version to check
* @param string $questionbankentryid Entry to check against
* @return bool
*/
public static function is_latest(string $version, string $questionbankentryid): bool {
global $DB;
$sql = 'SELECT MAX(version) AS max
FROM {question_versions}
WHERE questionbankentryid = ?';
$latestversion = $DB->get_record_sql($sql, [$questionbankentryid]);
if (isset($latestversion->max)) {
return ($version === $latestversion->max) ? true : false;
}
return false;
}
/**
* Loads question version ids for current question.
*
* @param string $questionbankentryid Question bank entry id
* @return array $questionids Array containing question id as key and version as value.
*/
public static function load_versions(string $questionbankentryid): array {
global $DB;
$questionids = [];
$sql = 'SELECT version, questionid
FROM {question_versions}
WHERE questionbankentryid = ?
ORDER BY version';
$versions = $DB->get_records_sql($sql, [$questionbankentryid]);
foreach ($versions as $key => $version) {
$questionids[$version->questionid] = $key;
}
return $questionids;
}
/**
* Return the question ID from the array of id => version that corresponds to the requested version.
*
* If the requested version is question_preview_options::ALWAYS_LATEST, this will return the latest version.
*
* @param array $versions
* @param int $restartversion
* @return ?int
*/
public static function get_restart_id(array $versions, int $restartversion): ?int {
if ($restartversion === question_preview_options::ALWAYS_LATEST) {
return array_key_last($versions);
} else {
return array_search($restartversion, $versions) ?: null;
}
}
}
@@ -0,0 +1,70 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qbank_previewquestion\output;
use context;
use qbank_previewquestion\helper;
use qbank_previewquestion\question_preview_options;
/**
* Class renderer for rendering preview url
*
* @package qbank_previewquestion
* @copyright 2009 The Open University
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends \plugin_renderer_base {
/**
* Render an icon, optionally with the word 'Preview' beside it, to preview a given question.
*
* @param int $questionid the id of the question to be previewed.
* @param context $context the context in which the preview is happening.
* Must be a course or category context.
* @param bool $showlabel if true, show the word 'Preview' after the icon.
* If false, just show the icon.
*/
public function question_preview_link($questionid, context $context, $showlabel) {
if ($showlabel) {
$alt = '';
$label = get_string('preview');
$attributes = [];
} else {
$alt = get_string('preview');
$label = '';
$attributes = ['title' => $alt];
}
$image = $this->pix_icon('t/preview', $alt, '', ['class' => 'iconsmall']);
$link = helper::question_preview_url($questionid, null, null, null, null, $context, null,
question_preview_options::ALWAYS_LATEST);
$action = new \popup_action('click', $link, 'questionpreview', helper::question_preview_popup_params());
return $this->action_link($link, $image . $label, $action, $attributes);
}
/**
* Render the preview page.
*
* @param array $previewdata
*/
public function render_preview_page($previewdata) {
return $this->render_from_template('qbank_previewquestion/preview_question', $previewdata);
}
}
@@ -0,0 +1,34 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qbank_previewquestion;
/**
* Class columns is the entrypoint for the columns.
*
* @package qbank_previewquestion
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class plugin_feature extends \core_question\local\bank\plugin_features_base{
public function get_question_actions($qbank): array {
return [
new preview_action($qbank)
];
}
}
@@ -0,0 +1,69 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qbank_previewquestion;
use core_question\local\bank\question_action_base;
/**
* Question bank columns for the preview action icon.
*
* @package qbank_previewquestion
* @copyright 2009 Tim Hunt
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class preview_action extends question_action_base {
/**
* @var string store this lang string for performance.
*/
protected $strpreview;
public function init(): void {
parent::init();
$this->strpreview = get_string('preview');
}
public function get_menu_position(): int {
return 100;
}
protected function get_url_icon_and_label(\stdClass $question): array {
if (!\question_bank::is_qtype_installed($question->qtype)) {
// It sometimes happens that people end up with junk questions
// in their question bank of a type that is no longer installed.
// We cannot do most actions on them, because that leads to errors.
return [null, null, null];
}
if (question_has_capability_on($question, 'use')) {
$context = $this->qbank->get_most_specific_context();
// Default previews to always use the latest question version, unless we are previewing specific versions from the
// question history.
if ($this->qbank->is_listing_specific_versions()) {
$requestedversion = $question->version;
} else {
$requestedversion = question_preview_options::ALWAYS_LATEST;
}
$url = helper::question_preview_url($question->id, null, null,
null, null, $context, $this->qbank->returnurl, $requestedversion);
return [$url, 't/preview', $this->strpreview];
} else {
return [null, null, null];
}
}
}
@@ -0,0 +1,32 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qbank_previewquestion\privacy;
/**
* Privacy Subsystem for qbank_previewquestion implementing null_provider.
*
* @package qbank_previewquestion
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
public static function get_reason(): string {
return 'privacy:metadata';
}
}
@@ -0,0 +1,144 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qbank_previewquestion;
use question_display_options;
/**
* Displays question preview options as default and set the options.
*
* Setting default, getting and setting user preferences in question preview options.
*
* @package qbank_previewquestion
* @copyright 2010 The Open University
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_preview_options extends question_display_options {
/** @var string the behaviour to use for this preview. */
public $behaviour;
/** @var number the maximum mark to use for this preview. */
public $maxmark;
/** @var int the variant of the question to preview. */
public $variant;
/** @var string prefix to append to field names to get user_preference names. */
const OPTIONPREFIX = 'question_preview_options_';
/** @var int Special value for question version ID to indicate that we should always use the latest version */
const ALWAYS_LATEST = 0;
/**
* Constructor.
* @param \stdClass $question
*/
public function __construct($question) {
$this->behaviour = 'deferredfeedback';
$this->maxmark = $question->defaultmark;
$this->variant = null;
$this->correctness = self::VISIBLE;
$this->marks = self::MARK_AND_MAX;
$this->markdp = get_config('quiz', 'decimalpoints');
$this->feedback = self::VISIBLE;
$this->numpartscorrect = $this->feedback;
$this->generalfeedback = self::VISIBLE;
$this->rightanswer = self::VISIBLE;
$this->history = self::HIDDEN;
$this->flags = self::HIDDEN;
$this->manualcomment = self::HIDDEN;
}
/**
* Names of the options we store in the user preferences table.
* @return array
*/
protected function get_user_pref_fields(): array {
return ['behaviour', 'correctness', 'marks', 'markdp', 'feedback', 'generalfeedback', 'rightanswer', 'history'];
}
/**
* Names and param types of the options we read from the request.
* @return array
*/
protected function get_field_types(): array {
return [
'behaviour' => PARAM_ALPHA,
'maxmark' => PARAM_LOCALISEDFLOAT,
'variant' => PARAM_INT,
'correctness' => PARAM_BOOL,
'marks' => PARAM_INT,
'markdp' => PARAM_INT,
'feedback' => PARAM_BOOL,
'generalfeedback' => PARAM_BOOL,
'rightanswer' => PARAM_BOOL,
'history' => PARAM_BOOL,
];
}
/**
* Load the value of the options from the user_preferences table.
*/
public function load_user_defaults(): void {
$defaults = get_config('question_preview');
foreach ($this->get_user_pref_fields() as $field) {
$this->$field = get_user_preferences(
self::OPTIONPREFIX . $field, $defaults->$field);
}
$this->numpartscorrect = $this->feedback;
}
/**
* Save a change to the user's preview options to the database.
* @param object $newoptions
*/
public function save_user_preview_options($newoptions): void {
foreach ($this->get_user_pref_fields() as $field) {
if (isset($newoptions->$field)) {
set_user_preference(self::OPTIONPREFIX . $field, $newoptions->$field);
}
}
}
/**
* Set the value of any fields included in the request.
*/
public function set_from_request(): void {
foreach ($this->get_field_types() as $field => $type) {
$this->$field = optional_param($field, $this->$field, $type);
}
$this->numpartscorrect = $this->feedback;
}
/**
* Parameters needed in the URL when continuing this preview.
*
* @return array URL fragment.
*/
public function get_url_params(): array {
$params = [];
foreach ($this->get_field_types() as $field => $notused) {
if ($field === 'behaviour' || $field === 'maxmark' || is_null($this->$field)) {
continue;
}
$params[$field] = $this->$field;
}
return $params;
}
}
@@ -0,0 +1,42 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component qbank_previewquestion, language 'en'
*
* @package qbank_previewquestion
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['alwayslatest'] = 'Always latest';
$string['newerversion'] = 'This preview is using an older version of the question. {$a->restartbutton}';
$string['pluginname'] = 'Preview question';
$string['privacy:metadata'] = 'The Preview question question bank plugin does not store any personal data.';
// Tag related errors.
$string['tagclosebutton'] = 'Close';
$string['tagerror'] = 'No question was found with following tags: {$a}. Please change or remove tags filtering.';
$string['tagsnotfound'] = 'Tags not found';
// Form string(s).
$string['previewoptions'] = 'Preview options';
$string['questionversion'] = 'Question version';
$string['restartnow'] = 'Use latest version';
$string['theoptionsyouselectonlyaffectthepreview'] = 'These settings are for testing the question. The options you select only affect the preview.';
// Preview title.
$string['versiontitle'] = 'Version {$a}';
$string['versiontitlelatest'] = 'Version {$a} (latest)';
+41
View File
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Callback methods for preview question component
*
* @package qbank_previewquestion
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
/**
* Get the current user preferences that are available
*
* @return array[]
*/
function qbank_previewquestion_user_preferences(): array {
return [
'core_question_preview_techinfo_collapsed' => [
'type' => PARAM_BOOL,
'null' => NULL_NOT_ALLOWED,
'default' => true,
'permissioncallback' => [core_user::class, 'is_current_user'],
],
];
}
+332
View File
@@ -0,0 +1,332 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This page displays a preview of a question
*
* The preview uses the option settings from the activity within which the question
* is previewed or the default settings if no activity is specified. The question session
* information is stored in the session as an array of subsequent states rather
* than in the database.
*
* @package qbank_previewquestion
* @copyright Alex Smith {@link http://maths.york.ac.uk/serving_maths}
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net> and numerous contributors.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->libdir . '/questionlib.php');
use \core\notification;
use qbank_previewquestion\form\preview_options_form;
use qbank_previewquestion\question_preview_options;
use qbank_previewquestion\helper;
/**
* The maximum number of variants previewable. If there are more variants than this for a question
* then we only allow the selection of the first x variants.
*
* @var integer
*/
define('QUESTION_PREVIEW_MAX_VARIANTS', 100);
\core_question\local\bank\helper::require_plugin_enabled('qbank_previewquestion');
// Get and validate question id.
$id = required_param('id', PARAM_INT);
$returnurl = optional_param('returnurl', null, PARAM_LOCALURL);
$restartversion = optional_param('restartversion', question_preview_options::ALWAYS_LATEST, PARAM_INT);
$question = question_bank::load_question($id);
if ($returnurl) {
$returnurl = new moodle_url($returnurl);
}
// Were we given a particular context to run the question in?
// This affects things like filter settings, or forced theme or language.
if ($cmid = optional_param('cmid', 0, PARAM_INT)) {
$cm = get_coursemodule_from_id(false, $cmid);
require_login($cm->course, false, $cm);
$context = context_module::instance($cmid);
} else if ($courseid = optional_param('courseid', 0, PARAM_INT)) {
require_login($courseid);
$context = context_course::instance($courseid);
} else {
require_login();
$category = $DB->get_record('question_categories', ['id' => $question->category], '*', MUST_EXIST);
$context = context::instance_by_id($category->contextid);
$PAGE->set_context($context);
// Note that in the other cases, require_login will set the correct page context.
}
question_require_capability_on($question, 'use');
$PAGE->set_pagelayout('popup');
// Get and validate display options.
$maxvariant = min($question->get_num_variants(), QUESTION_PREVIEW_MAX_VARIANTS);
$options = new question_preview_options($question);
$options->load_user_defaults();
$options->set_from_request();
$options->versioninfo = false;
$PAGE->set_url(helper::question_preview_url($id, $options->behaviour, $options->maxmark,
$options, $options->variant, $context, null, $restartversion));
// Get and validate existing preview, or start a new one.
$previewid = optional_param('previewid', 0, PARAM_INT);
if ($previewid) {
try {
$quba = question_engine::load_questions_usage_by_activity($previewid);
} catch (Exception $e) {
// This may not seem like the right error message to display, but
// actually from the user point of view, it makes sense.
throw new moodle_exception('submissionoutofsequencefriendlymessage', 'question',
helper::question_preview_url($question->id, $options->behaviour,
$options->maxmark, $options, $options->variant, $context, null, $restartversion), null, $e);
}
if ($quba->get_owning_context()->instanceid != $USER->id) {
throw new moodle_exception('notyourpreview', 'question');
}
$slot = $quba->get_first_question_number();
$usedquestion = $quba->get_question($slot, false);
if ($usedquestion->id != $question->id) {
throw new moodle_exception('questionidmismatch', 'question');
}
$question = $usedquestion;
$options->variant = $quba->get_variant($slot);
} else {
$quba = question_engine::make_questions_usage_by_activity(
'core_question_preview', context_user::instance($USER->id));
$quba->set_preferred_behaviour($options->behaviour);
$slot = $quba->add_question($question, $options->maxmark);
if ($options->variant) {
$options->variant = min($maxvariant, max(1, $options->variant));
} else {
$options->variant = rand(1, $maxvariant);
}
$quba->start_question($slot, $options->variant);
$transaction = $DB->start_delegated_transaction();
question_engine::save_questions_usage_by_activity($quba);
$transaction->allow_commit();
}
$options->behaviour = $quba->get_preferred_behaviour();
$options->maxmark = $quba->get_question_max_mark($slot);
$versionids = helper::load_versions($question->questionbankentryid);
// Create the settings form, and initialise the fields.
$optionsform = new preview_options_form(helper::question_preview_form_url($question->id, $context, $previewid, $returnurl),
[
'quba' => $quba,
'maxvariant' => $maxvariant,
'versions' => array_combine(array_values($versionids), array_values($versionids)),
'restartversion' => $restartversion,
]);
$optionsform->set_data($options);
// Process change of settings, if that was requested.
if ($newoptions = $optionsform->get_submitted_data()) {
// Set user preferences.
$options->save_user_preview_options($newoptions);
if (!isset($newoptions->variant)) {
$newoptions->variant = $options->variant;
}
$questionid = helper::get_restart_id($versionids, $restartversion);
if (isset($newoptions->saverestart)) {
helper::restart_preview($previewid, $questionid, $newoptions, $context, $returnurl, $newoptions->restartversion);
}
}
// Prepare a URL that is used in various places.
$actionurl = helper::question_preview_action_url($question->id, $quba->get_id(), $options, $context, $returnurl, $restartversion);
// Process any actions from the buttons at the bottom of the form.
if (data_submitted() && confirm_sesskey()) {
try {
if (optional_param('restart', false, PARAM_BOOL)) {
$questionid = helper::get_restart_id($versionids, $restartversion);
helper::restart_preview($previewid, $questionid, $options, $context, $returnurl, $restartversion);
} else if (optional_param('fill', null, PARAM_BOOL)) {
$correctresponse = $quba->get_correct_response($slot);
if (!is_null($correctresponse)) {
$quba->process_action($slot, $correctresponse);
$transaction = $DB->start_delegated_transaction();
question_engine::save_questions_usage_by_activity($quba);
$transaction->allow_commit();
}
redirect($actionurl);
} else if (optional_param('finish', null, PARAM_BOOL)) {
$quba->process_all_actions();
$quba->finish_all_questions();
$transaction = $DB->start_delegated_transaction();
question_engine::save_questions_usage_by_activity($quba);
$transaction->allow_commit();
redirect($actionurl);
} else {
$quba->process_all_actions();
$transaction = $DB->start_delegated_transaction();
question_engine::save_questions_usage_by_activity($quba);
$transaction->allow_commit();
$mdlscrollto = optional_param('mdlscrollto', '', PARAM_RAW);
if ($mdlscrollto !== '') {
$actionurl->param('mdlscrollto', (int) $mdlscrollto);
}
redirect($actionurl);
}
} catch (question_out_of_sequence_exception $e) {
throw new moodle_exception('submissionoutofsequencefriendlymessage', 'question', $actionurl);
} catch (Exception $e) {
// This sucks, if we display our own custom error message, there is no way
// to display the original stack trace.
$debuginfo = '';
if (!empty($e->debuginfo)) {
$debuginfo = $e->debuginfo;
}
throw new moodle_exception('errorprocessingresponses', 'question', $actionurl,
$e->getMessage(), $debuginfo);
}
}
if ($question->length) {
$displaynumber = '1';
} else {
$displaynumber = 'i';
}
$restartdisabled = [];
$finishdisabled = [];
$filldisabled = [];
if ($quba->get_question_state($slot)->is_finished()) {
$finishdisabled = ['disabled' => 'disabled'];
$filldisabled = ['disabled' => 'disabled'];
}
// If question type cannot give us a correct response, disable this button.
if (is_null($quba->get_correct_response($slot))) {
$filldisabled = ['disabled' => 'disabled'];
}
if (!$previewid) {
$restartdisabled = ['disabled' => 'disabled'];
}
// Prepare technical info to be output.
$qa = $quba->get_question_attempt($slot);
$technical = [];
$technical[] = get_string('behaviourbeingused', 'question',
question_engine::get_behaviour_name($qa->get_behaviour_name()));
$technical[] = get_string('technicalinfominfraction', 'question', $qa->get_min_fraction());
$technical[] = get_string('technicalinfomaxfraction', 'question', $qa->get_max_fraction());
$technical[] = get_string('technicalinfovariant', 'question', $qa->get_variant());
$technical[] = get_string('technicalinfoquestionsummary', 'question', s($qa->get_question_summary()));
$technical[] = get_string('technicalinforightsummary', 'question', s($qa->get_right_answer_summary()));
$technical[] = get_string('technicalinforesponsesummary', 'question', s($qa->get_response_summary()));
$technical[] = get_string('technicalinfostate', 'question', '' . $qa->get_state());
// Start output.
$title = get_string('previewquestion', 'question', format_string($question->name));
$headtags = question_engine::initialise_js() . $quba->render_question_head_html($slot);
$PAGE->set_title($title);
$PAGE->set_heading($title);
echo $OUTPUT->header();
$previewdata = [];
$previewdata['questionicon'] = print_question_icon($question);
$previewdata['questionidumber'] = $question->idnumber;
$previewdata['questiontitle'] = $question->name;
$versioninfo = new \core_question\output\question_version_info($question);
$previewdata['versiontitle'] = $versioninfo->export_for_template($OUTPUT);
if ($versioninfo->version !== $versioninfo->latestversion) {
if ($restartversion == question_preview_options::ALWAYS_LATEST) {
$newerversionparams = (object) [
'currentversion' => $question->version,
'latestversion' => max($versionids),
'restartbutton' => $OUTPUT->render_from_template('qbank_previewquestion/restartbutton', []),
];
$newversionurl = clone $actionurl;
$newversionurl->param('restart', 1);
$previewdata['newerversionurl'] = $newversionurl;
$previewdata['newerversion'] = get_string('newerversion', 'qbank_previewquestion', $newerversionparams);
}
}
$previewdata['actionurl'] = $actionurl;
$previewdata['session'] = sesskey();
$previewdata['slot'] = $slot;
// Output of the question.
$previewdata['question'] = $quba->render_question($slot, $options, $displaynumber);
$previewdata['restartdisabled'] = html_writer::attributes($restartdisabled);
$previewdata['finishdisabled'] = html_writer::attributes($finishdisabled);
$previewdata['filldisabled'] = html_writer::attributes($filldisabled);
// Output the technical info.
$previewdata['techinfo'] = print_collapsible_region_start('', 'techinfo', get_string('technicalinfo', 'question'),
'core_question_preview_techinfo_collapsed', true, true, $OUTPUT->help_icon('technicalinfo', 'question'));
foreach ($technical as $info) {
$previewdata['techinfo'] .= html_writer::tag('p', $info, ['class' => 'notifytiny']);
}
$previewdata['techinfo'] .= print_collapsible_region_end(true);
// Display the settings form.
$previewdata['options'] = $optionsform->render();
list($comment, $extraelements) = helper::get_preview_extra_elements($question, $COURSE->id);
if (!empty($comment)) {
$previewdata['comments'] = $comment;
}
if (!empty($extraelements)) {
$elements = [];
foreach ($extraelements as $extraelement) {
$element = new stdClass();
$element->extrapreviewelements = $extraelement;
$elements[] = $element;
}
$previewdata['extrapreviewelements'] = $elements;
}
$previewdata['redirect'] = false;
if (!is_null($returnurl)) {
$previewdata['redirect'] = true;
$previewdata['redirecturl'] = $returnurl;
}
$closeurl = new moodle_url('/question/edit.php', ['courseid' => $COURSE->id]);
echo $PAGE->get_renderer('qbank_previewquestion')->render_preview_page($previewdata);
// Log the preview of this question.
$event = \core\event\question_viewed::create_from_question_instance($question, $context);
$event->trigger();
$PAGE->requires->js_call_amd('qbank_previewquestion/preview', 'init', [$previewdata['redirect'], $closeurl->__toString()]);
echo $OUTPUT->footer();
@@ -0,0 +1,116 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template qbank_previewquestion/preview_question
The preview page for the question type preview.
* actionurl - Url to post to
* session - Moodle session
* slot - The identifying number of the first question that was added to this usage
* question - The html of the actual question from the engine
* questionicon - The icon of the question type
* questiontitle - The name of the question
* versiontitle - The string for displaying the version
* questionidumber - The idnumber of the question
* restartdisabled - The attributes to enable or disable the button, same for finishdisabled and filldisabled
* techinfo - Technical information like fraction, state, behaviour etc
* exporttoxml - Link to export the question to xml
* redirecturl - Url to the base view
* comments - Comments api html
* extrapreviewelements - Any plugin implementing the callback and sending extra html to view something in the preview page
Example context (json):
{
"actionurl": "/",
"session": "E2PwCfrnzz",
"slot": "1",
"question": "<div>question html</div>",
"questionicon": "<i class='icon fa fa-search-plus fa-fw' title='Preview question' aria-label='Preview question'></i>",
"questiontitle": "Question title",
"versiontitle": {
"versioninfo": "Version 3 (latest)"
},
"questionidumber": "qidnumber1",
"restartdisabled": "disabled='disabled'",
"finishdisabled": "disabled='disabled'",
"filldisabled": "disabled='disabled'",
"techinfo": "<div>Behaviour being used: Deferred feedback</div>",
"redirecturl": "/",
"exporttoxml": "Download this question in Moodle XML format",
"comments": "html from comments api",
"extrapreviewelements": "<div>callback to get html from plugins need to show info in preview</div>"
}
}}
<form id="responseform" method="post" action="{{{actionurl}}}" enctype="multipart/form-data" autocomplete="off">
<div class="d-flex">
<h2 class="mt-2">{{{questionicon}}}</h2>
<h2 class="ml-2 mt-2"> {{questiontitle}}</h2>
<h3 class="px-2 py-1 ml-2 mt-2">
{{#versiontitle}}
{{>core_question/question_version_info}}
{{/versiontitle}}
</h3>
</div>
{{#newerversion}}
<div class="alert alert-primary">
{{{newerversion}}}
</div>
{{/newerversion}}
<div class="d-flex">
<div class="bg-primary text-white h-50 px-2 mt-n2">
<span class="accesshide">ID number</span>
{{questionidumber}}
</div>
</div>
<br>
<div>
<input type="hidden" name="sesskey" value="{{session}}">
<input type="hidden" name="slots" value="{{slot}}">
<input type="hidden" name="scrollpos" value="" id="scrollpos">
</div>
{{{question}}}
<div id="previewcontrols" class="controls input-group">
<input type="submit" name="restart" value="{{#str}} restart, question{{/str}}" class="btn btn-secondary mr-1 mb-1" id="id_restart_question_preview" {{{restartdisabled}}}>
<input type="submit" name="save" value="{{#str}} save, question{{/str}}" class="btn btn-secondary mr-1 mb-1" id="id_save_question_preview" {{{finishdisabled}}}>
<input type="submit" name="fill" value="{{#str}} fillincorrect, question{{/str}}" class="btn btn-secondary mr-1 mb-1" {{{filldisabled}}}>
<input type="submit" name="finish" value="{{#str}} submitandfinish, question{{/str}}" class="btn btn-secondary mr-1 mb-1" id="id_finish_question_preview" {{{finishdisabled}}}>
{{^redirect}}
<input type="button" name="close" value="{{#str}} closepreview, question{{/str}}" class="btn btn-secondary mr-1 mb-1" id="close-previewquestion-page">
{{/redirect}}
{{#redirect}}
<a href="{{{redirecturl}}}" class="btn btn-secondary mr-1 mb-1" role="button">{{#str}} closepreview, question{{/str}}</a>
{{/redirect}}
</div>
</form>
<br>
{{#comments}}
<a data-toggle="collapse" href="#commentcollapse" role="button" aria-expanded="false" aria-controls="commentcollapse">
{{#pix}} t/collapsed, core {{/pix}}
{{#str}} commentplural, qbank_comment{{/str}}
</a>
<div class="collapse" id="commentcollapse">
{{{comments}}}
</div>
{{{options}}}
{{/comments}}
{{^comments}}
{{{options}}}
{{/comments}}
{{{techinfo}}}
{{#extrapreviewelements}}
{{{extrapreviewelements}}}
{{/extrapreviewelements}}
@@ -0,0 +1,27 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template qbank_previewquestion/restartbutton
A button to restart the preview.
Example context (json):
{
}
}}
<input type="submit" name="restart" value="{{#str}}restartnow, qbank_previewquestion{{/str}}"
class="btn btn-secondary" id="id_restartnow_question_preview">
@@ -0,0 +1,169 @@
@qbank @qbank_previewquestion
Feature: A teacher can preview questions in the question bank
In order to ensure the questions are properly created
As a teacher
I need to preview the questions
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | weeks |
And the following "activities" exist:
| activity | name | course | idnumber |
| quiz | Test quiz | C1 | quiz1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Test questions |
And the following "questions" exist:
| questioncategory | qtype | name |
| Test questions | numerical | Test question to be previewed |
And I am on the "Test quiz" "mod_quiz > question bank" page logged in as "teacher1"
When I choose "Preview" action for "Test question to be previewed" in the question bank
Scenario: Question preview shows the question and other information
Then the state of "What is pi to two d.p.?" question is shown as "Not yet answered"
And I should see "(latest)"
And I should see "Marked out of 1.00"
And I should see "Technical information"
And I should see "Display options"
And I should see "Preview options"
And I should see "Comments"
And I click on "Comments" "link"
And I should see "Save comment"
And I should see "ID number"
And "Numerical" "icon" should exist
And I should see "Version"
And I click on "Preview options" "link"
And I should see "These settings are for testing the question. The options you select only affect the preview."
And I should see "Question version"
Scenario: Preview lets the teacher see what happens when an answer is saved
When I set the field "Answer:" to "1"
And I press "Save"
Then the state of "What is pi to two d.p.?" question is shown as "Answer saved"
Scenario: Preview lets the teacher see what happens when an answer is submitted
When I set the field "Answer:" to "3.14"
And I press "Submit and finish"
Then the state of "What is pi to two d.p.?" question is shown as "Correct"
Scenario: Preview lets the teacher see what happens with different review options
Given I set the field "Answer:" to "3.14"
And I press "Submit and finish"
And I press "Display options"
When I set the field "Whether correct" to "Not shown"
And I set the field "Decimal places in grades" to "5"
And I press "Update display options"
And I set the field "Answer:" to "3.14"
And I press "Submit and finish"
Then the state of "What is pi to two d.p.?" question is shown as "Complete"
And I should see "1.00000"
Scenario: Preview lets the teacher see what happens with different behaviours
When I press "Preview options"
And I set the field "How questions behave" to "Immediate feedback"
And I set the field "Marked out of" to "3"
And I press "Save preview options and start again"
And I set the field "Answer:" to "3.1"
And I press "Check"
Then the state of "What is pi to two d.p.?" question is shown as "Incorrect"
And I should see "Mark 0.00 out of 3.00"
And I should see "Not accurate enough."
Scenario: Preview lets the teacher "Start again" while previewing
Given I set the field "Answer:" to "1"
And I press "Submit and finish"
When I press "Start again"
Then the state of "What is pi to two d.p.?" question is shown as "Not yet answered"
Scenario: Preview lets the teacher "Fill in correct response" while previewing
When I press "Fill in correct responses"
Then the field "Answer:" matches value "3.14"
Scenario: Preview a question with very small grade
When I press "Preview options"
And I set the field "Marked out of" to "0.00000123456789"
And I press "Save preview options and start again"
Then the field "Marked out of" matches value "0.00000123456789"
Scenario: Question version is updated when edited and teacher can change question version
Given I should see "Version 1"
And I press "Close preview"
And I choose "Edit question" action for "Test question to be previewed" in the question bank
And I set the field "Question name" to "New version"
And I set the field "Question text" to "New text version"
And I click on "submitbutton" "button"
And I choose "Preview" action for "New version" in the question bank
When I expand all fieldsets
And I should see "Version 2"
And I should see "(latest)"
And I should see "New version"
And I should see "New text version"
And I should not see "Test question to be previewed"
And I should not see "Version 1"
And I should see "1" in the "Question version" "select"
And I should see "2" in the "Question version" "select"
And I set the field "Question version" to "1"
And I press "Save preview options and start again"
Then I should see "Version 1"
And I should not see "Version 2"
And I should not see "(latest)"
Scenario: The preview always uses the latest question version by default.
Given the following "core_question > updated questions" exist:
| questioncategory | question | questiontext |
| Test questions | Test question to be previewed | Question version 2 |
And I should see "Version 1 (latest)"
And I expand all fieldsets
And the field "Question version" matches value "Always latest"
And I set the field "Answer:" to "3.14"
And I press "Submit and finish"
And I should see "Version 1"
When I press "Start again"
Then I should not see "Version 1"
And I should see "Version 2 (latest)"
Scenario: Detect a newer version during always latest preview and offer to switch to the latest
Given I should not see "This preview is using version 1"
And the following "core_question > updated questions" exist:
| questioncategory | question | questiontext |
| Test questions | Test question to be previewed | Question version 2 |
And I should see "Version 1 (latest)"
And I set the field "Answer:" to "3.14"
When I press "Submit and finish"
And I should see "This preview is using an older version of the question."
And I press "Use latest version"
Then I should not see "Version 1"
And I should see "Version 2 (latest)"
Scenario: Previewing from the question history will not always show the latest version
Given I press "Close preview"
And the following "core_question > updated questions" exist:
| questioncategory | question | questiontext |
| Test questions | Test question to be previewed | Question version 2 |
And I choose "History" action for "Test question to be previewed" in the question bank
And I choose "Preview" action for "Test question to be previewed" in the question bank
And I should see "Version 1 (of 2)"
And I expand all fieldsets
And the field "Question version" matches value "1"
And I set the field "Answer:" to "3.14"
And I press "Submit and finish"
And I should see "Version 1 (of 2)"
And I should not see "The latest version is 2."
And the following "core_question > updated questions" exist:
| questioncategory | question | questiontext |
| Test questions | Test question to be previewed | Question version 3 |
When I press "Start again"
Then I should see "Version 1 (of 3)"
And I should not see "Version 3 (latest)"
Scenario: Question preview can be closed
And I press "Close preview"
Then I should not see "(latest)"
And I should see "Test quiz"
@@ -0,0 +1,43 @@
@qbank @qbank_previewquestion
Feature: Use the qbank plugin manager page for previewquestion
In order to check the plugin behaviour with enable and disable
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "activities" exist:
| activity | name | course | idnumber |
| quiz | Test quiz | C1 | quiz1 |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Test questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | First question | Answer the first question |
Scenario: Enable/disable previewquestion column from the base view
Given I log in as "admin"
When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
And I should see "Preview question"
And I click on "Disable" "link" in the "Preview question" "table_row"
And I am on the "Test quiz" "mod_quiz > question bank" page
Then the "Preview" item should not exist in the "Edit" action menu of the "First question" "table_row"
And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
And I click on "Enable" "link" in the "Preview question" "table_row"
And I am on the "Test quiz" "mod_quiz > question bank" page
And the "Preview" item should exist in the "Edit" action menu of the "First question" "table_row"
Scenario: Enable/disable preview button from question edit form
Given I log in as "admin"
When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
And I should see "Preview question"
And I click on "Disable" "link" in the "Preview question" "table_row"
And I am on the "Test quiz" "mod_quiz > question bank" page
And I choose "Edit question" action for "First question" in the question bank
Then I should not see "Preview" in the "region-main" "region"
And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
And I click on "Enable" "link" in the "Preview question" "table_row"
And I am on the "Test quiz" "mod_quiz > question bank" page
And I choose "Edit question" action for "First question" in the question bank
And I should see "Preview" in the "region-main" "region"
@@ -0,0 +1,271 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qbank_previewquestion;
use context_course;
use moodle_url;
use core\plugininfo\qbank;
use question_bank;
use question_engine;
use stdClass;
/**
* Helper tests for question preview.
*
* @package qbank_previewquestion
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \qbank_previewquestion\helper
*/
class qbank_preview_helper_test extends \advanced_testcase {
/**
* @var bool|\context|\context_course $context
*/
public $context;
/**
* @var object $questiondata;
*/
public $questiondata;
/**
* @var \question_usage_by_activity $quba
*/
public $quba;
/**
* @var question_preview_options $options
*/
public $options;
/**
* @var \moodle_url $returnurl
*/
public $returnurl;
/**
* Test set up.
*
* This is executed before running any test in this file.
*/
public function setUp(): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$generator = $this->getDataGenerator();
$questiongenerator = $generator->get_plugin_generator('core_question');
// Create a course.
$course = $generator->create_course();
$this->context = context_course::instance($course->id);
// Create a question in the default category.
$contexts = new \core_question\local\bank\question_edit_contexts($this->context);
$cat = question_make_default_categories($contexts->all());
$this->questiondata = $questiongenerator->create_question('numerical', null,
['name' => 'Example question', 'category' => $cat->id]);
$this->quba = question_engine::make_questions_usage_by_activity('core_question_preview',
\context_user::instance($USER->id));
$this->options = new question_preview_options($this->questiondata);
$this->options->load_user_defaults();
$this->options->set_from_request();
$this->returnurl = new moodle_url('/question/edit.php');
}
/**
* Test the preview action url from the helper class.
*
* @covers ::question_preview_action_url
*/
public function test_question_preview_action_url(): void {
$actionurl = helper::question_preview_action_url($this->questiondata->id, $this->quba->get_id(), $this->options,
$this->context, $this->returnurl, question_preview_options::ALWAYS_LATEST);
$params = [
'id' => $this->questiondata->id,
'previewid' => $this->quba->get_id(),
'returnurl' => $this->returnurl,
'courseid' => $this->context->instanceid,
'restartversion' => question_preview_options::ALWAYS_LATEST,
];
$params = array_merge($params, $this->options->get_url_params());
$expectedurl = new moodle_url('/question/bank/previewquestion/preview.php', $params);
$this->assertEquals($expectedurl, $actionurl);
}
/**
* Test the preview action url from the helper class when no restartversion is passed.
*
* @covers ::question_preview_action_url
*/
public function test_question_preview_action_url_no_restartversion(): void {
$actionurl = helper::question_preview_action_url($this->questiondata->id, $this->quba->get_id(), $this->options,
$this->context, $this->returnurl);
$params = [
'id' => $this->questiondata->id,
'previewid' => $this->quba->get_id(),
'returnurl' => $this->returnurl,
'courseid' => $this->context->instanceid
];
$params = array_merge($params, $this->options->get_url_params());
$expectedurl = new moodle_url('/question/bank/previewquestion/preview.php', $params);
$this->assertEquals($expectedurl, $actionurl);
}
/**
* Test the preview form url from the helper class.
*
* @covers ::question_preview_form_url
*/
public function test_question_preview_form_url(): void {
$formurl = helper::question_preview_form_url(
$this->questiondata->id, $this->context, $this->quba->get_id(), $this->returnurl);
$params = [
'id' => $this->questiondata->id,
'previewid' => $this->quba->get_id(),
'returnurl' => $this->returnurl,
'courseid' => $this->context->instanceid
];
$expectedurl = new moodle_url('/question/bank/previewquestion/preview.php', $params);
$this->assertEquals($expectedurl, $formurl);
}
/**
* Test the preview url from the helper class.
*
* @covers ::question_preview_url
*/
public function test_question_preview_url(): void {
$previewurl = helper::question_preview_url($this->questiondata->id, $this->options->behaviour, $this->options->maxmark,
$this->options, $this->options->variant, $this->context, null, question_preview_options::ALWAYS_LATEST);
$params = [
'id' => $this->questiondata->id,
'behaviour' => $this->options->behaviour,
'maxmark' => $this->options->maxmark,
'courseid' => $this->context->instanceid,
'restartversion' => question_preview_options::ALWAYS_LATEST,
];
// Extra params for options.
$params['correctness'] = $this->options->correctness;
$params['marks'] = $this->options->marks;
$params['markdp'] = $this->options->markdp;
$params['feedback'] = (bool) $this->options->feedback;
$params['generalfeedback'] = (bool) $this->options->generalfeedback;
$params['rightanswer'] = (bool) $this->options->rightanswer;
$params['history'] = (bool) $this->options->history;
$expectedurl = new moodle_url('/question/bank/previewquestion/preview.php', $params);
$this->assertEquals($expectedurl, $previewurl);
}
/**
* Test the preview url from the helper class.
*
* @covers ::question_preview_url
*/
public function test_question_preview_url_no_restartversion(): void {
$previewurl = helper::question_preview_url($this->questiondata->id, $this->options->behaviour, $this->options->maxmark,
$this->options, $this->options->variant, $this->context, null);
$params = [
'id' => $this->questiondata->id,
'behaviour' => $this->options->behaviour,
'maxmark' => $this->options->maxmark,
'courseid' => $this->context->instanceid,
];
// Extra params for options.
$params['correctness'] = $this->options->correctness;
$params['marks'] = $this->options->marks;
$params['markdp'] = $this->options->markdp;
$params['feedback'] = (bool) $this->options->feedback;
$params['generalfeedback'] = (bool) $this->options->generalfeedback;
$params['rightanswer'] = (bool) $this->options->rightanswer;
$params['history'] = (bool) $this->options->history;
$expectedurl = new moodle_url('/question/bank/previewquestion/preview.php', $params);
$this->assertEquals($expectedurl, $previewurl);
}
/**
* Test the preview comment callback if available.
*
* @covers ::get_preview_extra_elements
*/
public function test_get_preview_extra_elements(): void {
global $PAGE;
$PAGE->set_url('/');
$question = \question_bank::load_question($this->questiondata->id);
list($comment, $extraelements) = helper::get_preview_extra_elements($question, $this->context->instanceid);
if (qbank::is_plugin_enabled('qbank_comment')) {
$this->assertStringContainsString("comment-area", $comment);
} else {
$this->assertEquals('', $comment);
}
}
/**
* Test method load_versions().
*
* @covers ::load_versions
*/
public function test_load_versions(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$qcat1 = $generator->create_question_category(['name' => 'My category', 'sortorder' => 1, 'idnumber' => 'myqcat']);
$questiongenerated = $generator->create_question('description', null, ['name' => 'q1', 'category' => $qcat1->id]);
$qtypeobj = question_bank::get_qtype($questiongenerated->qtype);
$question = question_bank::load_question($questiongenerated->id);
$versionids = helper::load_versions($question->questionbankentryid);
$this->assertEquals([
$question->id => 1,
], $versionids);
$fromform = new stdClass();
$fromform->name = 'Name edited';
$fromform->category = $qcat1->id;
$questiontwo = $qtypeobj->save_question($questiongenerated, $fromform);
$versionids = helper::load_versions($question->questionbankentryid);
$this->assertSame([
$question->id => 1,
$questiontwo->id => 2,
], $versionids);
}
/**
* Test method get_restart_id().
*
* This should return the value of the specified version number, or the latest version if ALWAYS_LATEST is passed.
*
* @covers ::get_restart_id
* @return void
*/
public function test_get_restart_id(): void {
$versions = [
100 => 1,
200 => 2,
300 => 3
];
$this->assertEquals(100, helper::get_restart_id($versions, 1));
$this->assertEquals(200, helper::get_restart_id($versions, 2));
$this->assertEquals(300, helper::get_restart_id($versions, 3));
$this->assertEquals(300, helper::get_restart_id($versions, question_preview_options::ALWAYS_LATEST));
$this->assertNull(helper::get_restart_id($versions, 4));
$this->assertNull(helper::get_restart_id([], 1));
$this->assertNull(helper::get_restart_id([], question_preview_options::ALWAYS_LATEST));
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Version information for qbank_previewquestion.
*
* @package qbank_previewquestion
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'qbank_previewquestion';
$plugin->version = 2024042200;
$plugin->requires = 2024041600;
$plugin->maturity = MATURITY_STABLE;