Merge branch 'MDL-74054-master-fixes' of https://github.com/marxjohnson/moodle

This commit is contained in:
Andrew Nicols
2023-09-22 10:53:55 +08:00
119 changed files with 4673 additions and 998 deletions
+27 -38
View File
@@ -28,7 +28,9 @@ namespace mod_quiz\question\bank;
defined('MOODLE_INTERNAL') || die();
use core\output\datafilter;
use core_question\local\bank\column_base;
use core_question\local\bank\condition;
use core_question\local\bank\column_manager_base;
use core_question\local\bank\question_version_status;
use mod_quiz\question\bank\filter\custom_category_condition;
use qbank_managecategories\category_condition;
@@ -79,7 +81,7 @@ class custom_view extends \core_question\local\bank\view {
}
}
$this->init_required_columns();
$this->init_columns($this->wanted_columns(), $this->heading_column());
parent::__construct($contexts, $pageurl, $course, $cm, $params, $extraparams);
[$this->quiz, ] = get_module_from_cmid($cm->id);
$this->set_quiz_has_attempts(quiz_has_attempts($this->quiz->id));
@@ -87,55 +89,42 @@ class custom_view extends \core_question\local\bank\view {
}
/**
* Init required columns.
* Just use the base column manager in this view.
*
* @return void
*/
protected function init_required_columns(): void {
// Override core columns.
$this->corequestionbankcolumns = [
'add_action_column',
'checkbox_column',
'question_type_column',
'question_name_text_column',
'preview_action_column'
];
protected function init_column_manager(): void {
$this->columnmanager = new column_manager_base();
}
/**
* Get class for each question bank columns.
* Don't display plugin controls.
*
* @return array
* @param \core\context $context
* @param int $categoryid
* @return string
*/
protected function get_class_for_columns(): array {
protected function get_plugin_controls(\core\context $context, int $categoryid): string {
return '';
}
protected function get_question_bank_plugins(): array {
$questionbankclasscolumns = [];
if (question_get_display_preference('qbshowtext', 0, PARAM_INT, new \moodle_url(''))) {
$this->corequestionbankcolumns[] = 'question_text_row';
}
foreach ($this->corequestionbankcolumns as $fullname) {
$shortname = $fullname;
if (class_exists('mod_quiz\\question\\bank\\' . $fullname)) {
$fullname = 'mod_quiz\\question\\bank\\' . $fullname;
$questionbankclasscolumns[$shortname] = new $fullname($this);
} else if (class_exists('core_question\\local\\bank\\' . $fullname)) {
$fullname = 'core_question\\local\\bank\\' . $fullname;
$questionbankclasscolumns[$shortname] = new $fullname($this);
} else {
$questionbankclasscolumns[$shortname] = '';
$customviewcolumns = [
'mod_quiz\question\bank\add_action_column' . column_base::ID_SEPARATOR . 'add_action_column',
'core_question\local\bank\checkbox_column' . column_base::ID_SEPARATOR . 'checkbox_column',
'qbank_viewquestiontype\question_type_column' . column_base::ID_SEPARATOR . 'question_type_column',
'mod_quiz\question\bank\question_name_text_column' . column_base::ID_SEPARATOR . 'question_name_text_column',
'mod_quiz\question\bank\preview_action_column' . column_base::ID_SEPARATOR . 'preview_action_column',
];
foreach ($customviewcolumns as $columnid) {
[$columnclass, $columnname] = explode(column_base::ID_SEPARATOR, $columnid, 2);
if (class_exists($columnclass)) {
$questionbankclasscolumns[$columnid] = $columnclass::from_column_name($this, $columnname);
}
}
return $questionbankclasscolumns;
}
/**
* Get the list of qbank plugins with available objects for features.
*
* @return array
*/
protected function get_question_bank_plugins(): array {
// The view is too crowded, exclude some columns.
$questionbankclasscolumns = parent::get_question_bank_plugins();
$questionbankclasscolumns = array_intersect_key($questionbankclasscolumns, array_flip($this->corequestionbankcolumns));
return $questionbankclasscolumns;
}
+8 -1
View File
@@ -2549,15 +2549,22 @@ function mod_quiz_output_fragment_question_data(array $args): string {
$thispageurl = new \moodle_url('/mod/quiz/edit.php', ['cmid' => $cmid]);
$thiscontext = \context_module::instance($cmid);
$contexts = new \core_question\local\bank\question_edit_contexts($thiscontext);
$defaultcategory = question_make_default_categories($contexts->all());
$params['cat'] = implode(',', [$defaultcategory->id, $defaultcategory->contextid]);
$course = get_course($params['courseid']);
[, $cm] = get_module_from_cmid($cmid);
$params['tabname'] = 'questions';
// Custom question bank View.
$viewclass = clean_param($args['view'], PARAM_NOTAGS);
$questionbank = new $viewclass($contexts, $thispageurl, $course, $cm, $params, $extraparams);
// Question table.
return $questionbank->display_questions_table();
$questionbank->add_standard_search_conditions();
ob_start();
$questionbank->display_question_list();
return ob_get_clean();
}
/**
+43 -29
View File
@@ -801,7 +801,7 @@ table.quizreviewsummary td.cell {
margin: 0 0.2em;
}
#page-mod-quiz-edit #categoryquestions .questionname,
#page-mod-quiz-edit .question-bank-table .questionname,
#page-mod-quiz-edit ul.slots li.activity div.activityinstance .questionname {
font-weight: bold;
color: #555;
@@ -845,91 +845,105 @@ table.quizreviewsummary td.cell {
display: none;
}
#categoryquestions .header {
#page-mod-quiz-edit .question-bank-table {
width: auto;
}
#page-mod-quiz-edit .question-bank-table .header {
text-align: center;
padding: 0 2px;
border: 0 none;
vertical-align: top;
width: auto;
}
#categoryquestions .header.checkbox {
#page-mod-quiz-edit .question-bank-table .header.checkbox {
vertical-align: bottom;
text-align: center;
}
#categoryquestions .header.qtype {
#page-mod-quiz-edit .question-bank-table .header.qtype .sorters {
white-space: nowrap;
}
#categoryquestions th .sorters {
#page-mod-quiz-edit .question-bank-table th .sorters {
font-weight: normal;
font-size: 0.8em;
}
#categoryquestions td.modifiername,
#categoryquestions td.creatorname,
#categoryquestions td.questionlastused {
#page-mod-quiz-edit .question-bank-table td.modifiername,
#page-mod-quiz-edit .question-bank-table td.creatorname,
#page-mod-quiz-edit .question-bank-table td.questionlastused {
line-height: 1em;
}
#categoryquestions td.modifiername span.date,
#categoryquestions td.creatorname span.date {
#page-mod-quiz-edit .question-bank-table td.modifiername span.date,
#page-mod-quiz-edit .question-bank-table td.creatorname span.date {
font-weight: normal;
font-size: 0.8em;
}
table#categoryquestions {
width: 100%;
#page-mod-quiz-edit table.question-bank-table {
table-layout: fixed;
overflow-x: visible;
border-collapse: separate;
border-spacing: 0;
}
#categoryquestions .iconcol {
width: 15px;
#page-mod-quiz-edit .question-bank-table .iconcol {
width: 16px;
text-align: center;
padding: 0;
}
#page-mod-quiz-edit .question-bank-table .iconcol .icon {
width: 16px;
}
#page-mod-quiz-edit .question-bank-table .checkbox {
width: 32px;
text-align: center;
padding: 0;
}
#categoryquestions .checkbox {
width: 19px;
text-align: center;
padding: 0;
}
#categoryquestions .editmenu {
#page-mod-quiz-edit .question-bank-table .editmenu {
width: 5em;
}
#categoryquestions .qtype {
#page-mod-quiz-edit .question-bank-table .qtype {
text-align: center;
}
#categoryquestions .qtype {
#page-mod-quiz-edit .question-bank-table .qtype {
width: 28px;
padding: 0;
}
#categoryquestions .questiontext {
#page-mod-quiz-edit .question-bank-table .questionnametext {
padding: 0 0.2em;
}
#page-mod-quiz-edit .question-bank-table .questiontext {
position: relative;
zoom: 1;
padding-left: 0.3em;
padding: 0.3em;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#categoryquestions .questionname {
#page-mod-quiz-edit .question-bank-table .questionname {
white-space: nowrap;
overflow: hidden;
zoom: 1;
position: relative;
}
#categoryquestions .questiontext p {
#page-mod-quiz-edit .question-bank-table .questiontext p {
margin: 0;
}
#page-mod-quiz-edit table#categoryquestions td,
#page-mod-quiz-edit table#categoryquestions th {
#page-mod-quiz-edit table.question-bank-table td,
#page-mod-quiz-edit table.question-bank-table th {
overflow: hidden;
white-space: nowrap;
}
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
define("core_question/refresh_ui",["exports","core/fragment","core/templates"],(function(_exports,_fragment,_templates){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Question bank UI refresh utility
*
* @module core_question/refresh_ui
* @copyright 2023 Catalyst IT Europe Ltd.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_fragment=_interopRequireDefault(_fragment),_templates=_interopRequireDefault(_templates);var _default={refresh:(uiRoot,returnUrl)=>new Promise(((resolve,reject)=>{const fragmentData=uiRoot.dataset,viewData={},sortData={};returnUrl&&returnUrl.searchParams.forEach(((value,key)=>{const sortItem=key.match(/sortdata\[([^\]]+)\]/);sortItem?sortData[sortItem.pop()]=value:viewData[key]=value})),viewData.sortdata=JSON.stringify(sortData),_fragment.default.loadFragment(fragmentData.component,fragmentData.callback,fragmentData.contextid,viewData).then(((html,js)=>(_templates.default.replaceNode(uiRoot,html,js),resolve(),html))).catch(reject)}))};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=refresh_ui.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"refresh_ui.min.js","sources":["../src/refresh_ui.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 * Question bank UI refresh utility\n *\n * @module core_question/refresh_ui\n * @copyright 2023 Catalyst IT Europe Ltd.\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Fragment from 'core/fragment';\nimport Templates from 'core/templates';\n\nexport default {\n /**\n * Reload the question bank UI, retaining the current filters and sort data.\n *\n * @param {Element} uiRoot The root element of the UI to be refreshed. Must contain \"component\", \"callback\" and \"contextid\" in\n * its data attributes, to be passed to the Fragment API.\n * @param {URL} returnUrl The url of the current page, containing filter and sort parameters.\n * @return {Promise} Resolved when the refresh is complete.\n */\n refresh: (uiRoot, returnUrl) => {\n return new Promise((resolve, reject) => {\n const fragmentData = uiRoot.dataset;\n const viewData = {};\n const sortData = {};\n if (returnUrl) {\n returnUrl.searchParams.forEach((value, key) => {\n // Match keys like 'sortdata[fieldname]' and convert them to an array,\n // because the fragment API doesn't like non-alphanum argument keys.\n const sortItem = key.match(/sortdata\\[([^\\]]+)\\]/);\n if (sortItem) {\n // The item returned by sortItem.pop() is the contents of the matching group, the field name.\n sortData[sortItem.pop()] = value;\n } else {\n viewData[key] = value;\n }\n });\n }\n viewData.sortdata = JSON.stringify(sortData);\n // We have to use then() there, as loadFragment doesn't appear to work with await.\n Fragment.loadFragment(fragmentData.component, fragmentData.callback, fragmentData.contextid, viewData)\n .then((html, js) => {\n Templates.replaceNode(uiRoot, html, js);\n resolve();\n return html;\n })\n .catch(reject);\n });\n }\n};\n"],"names":["refresh","uiRoot","returnUrl","Promise","resolve","reject","fragmentData","dataset","viewData","sortData","searchParams","forEach","value","key","sortItem","match","pop","sortdata","JSON","stringify","loadFragment","component","callback","contextid","then","html","js","replaceNode","catch"],"mappings":";;;;;;;4LA0Be,CASXA,QAAS,CAACC,OAAQC,YACP,IAAIC,SAAQ,CAACC,QAASC,gBACnBC,aAAeL,OAAOM,QACtBC,SAAW,GACXC,SAAW,GACbP,WACAA,UAAUQ,aAAaC,SAAQ,CAACC,MAAOC,aAG7BC,SAAWD,IAAIE,MAAM,wBACvBD,SAEAL,SAASK,SAASE,OAASJ,MAE3BJ,SAASK,KAAOD,SAI5BJ,SAASS,SAAWC,KAAKC,UAAUV,4BAE1BW,aAAad,aAAae,UAAWf,aAAagB,SAAUhB,aAAaiB,UAAWf,UACxFgB,MAAK,CAACC,KAAMC,yBACCC,YAAY1B,OAAQwB,KAAMC,IACpCtB,UACOqB,QAEVG,MAAMvB"}
+20 -31
View File
@@ -70,7 +70,10 @@ export const init = (
const filterSet = document.querySelector(`#${filterRegionId}`);
const filterCondition = {
const viewData = {
extraparams,
cmid,
view,
cat: defaultcategoryid,
courseid: defaultcourseid,
filter: {},
@@ -78,16 +81,15 @@ export const init = (
qpage: 0,
qperpage: perpage,
sortdata: {},
tabname: 'questions',
lastchanged: document.querySelector(SELECTORS.LASTCHANGED_FIELD)?.value ?? null,
};
let sortData = {};
const defaultSort = document.querySelector(SELECTORS.QUESTION_TABLE)?.dataset?.defaultsort;
if (defaultSort) {
filterCondition.sortData = JSON.parse(defaultSort);
sortData = JSON.parse(defaultSort);
}
let filterQuery = '';
/**
* Retrieve table data.
*
@@ -99,26 +101,20 @@ export const init = (
// use the default category filter condition.
if (filterdata) {
// Main join types.
filterCondition.jointype = parseInt(filterSet.dataset.filterverb, 10);
viewData.jointype = parseInt(filterSet.dataset.filterverb, 10);
delete filterdata.jointype;
// Retrieve filter info.
filterCondition.filter = filterdata;
viewData.filter = filterdata;
if (Object.keys(filterdata).length !== 0) {
if (!isNaN(filterCondition.jointype)) {
filterdata.jointype = filterCondition.jointype;
if (!isNaN(viewData.jointype)) {
filterdata.jointype = viewData.jointype;
}
updateUrlParams(filterdata);
}
}
// Load questions for first page.
const viewData = {
view: view,
cmid: cmid,
filtercondition: JSON.stringify(filterCondition),
extraparams: extraparams,
filterquery: filterQuery,
lastchanged: document.querySelector(SELECTORS.LASTCHANGED_FIELD)?.value ?? null
};
viewData.filter = JSON.stringify(filterdata);
viewData.sortdata = JSON.stringify(sortData);
Fragment.loadFragment(component, callback, contextId, viewData)
// Render questions for first page and pagination.
.then((questionhtml, jsfooter) => {
@@ -129,7 +125,7 @@ export const init = (
if (jsfooter === undefined) {
jsfooter = '';
}
Templates.replaceNodeContents(questionscontainer, questionhtml, jsfooter);
Templates.replaceNode(questionscontainer, questionhtml, jsfooter);
// Resolve filter promise.
if (pendingPromise) {
pendingPromise.resolve();
@@ -154,13 +150,6 @@ export const init = (
const filterQuery = JSON.stringify(filters);
url.searchParams.set('filter', filterQuery);
history.pushState(filters, '', url);
document.querySelectorAll(SELECTORS.BULK_ACTIONS).forEach(bulkAction => {
const actionUrl = new URL(bulkAction.formAction);
const returnUrl = new URL(actionUrl.searchParams.get('returnurl'));
returnUrl.searchParams.set('filter', filterQuery);
actionUrl.searchParams.set('returnurl', returnUrl);
bulkAction.formAction = actionUrl;
});
const editSwitch = document.querySelector(SELECTORS.EDIT_SWITCH);
if (editSwitch) {
const editSwitchUrlInput = document.querySelector(SELECTORS.EDIT_SWITCH_URL);
@@ -197,15 +186,15 @@ export const init = (
const clearLink = e.target.closest(Selectors.filterset.actions.resetFilters);
if (sortableLink) {
e.preventDefault();
const oldSort = filterCondition.sortdata;
filterCondition.sortdata = {};
filterCondition.sortdata[sortableLink.dataset.sortname] = sortableLink.dataset.sortorder;
const oldSort = sortData;
sortData = {};
sortData[sortableLink.dataset.sortname] = sortableLink.dataset.sortorder;
for (const sortname in oldSort) {
if (sortname !== sortableLink.dataset.sortname) {
filterCondition.sortdata[sortname] = oldSort[sortname];
sortData[sortname] = oldSort[sortname];
}
}
filterCondition.qpage = 0;
viewData.qpage = 0;
coreFilter.updateTableFromFilter();
}
if (paginationLink) {
@@ -213,7 +202,7 @@ export const init = (
const paginationURL = new URL(paginationLink.getAttribute("href"));
const qpage = paginationURL.searchParams.get('qpage');
if (paginationURL.search !== null) {
filterCondition.qpage = qpage;
viewData.qpage = qpage;
coreFilter.updateTableFromFilter();
}
}
+65
View File
@@ -0,0 +1,65 @@
// 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/>.
/**
* Question bank UI refresh utility
*
* @module core_question/refresh_ui
* @copyright 2023 Catalyst IT Europe Ltd.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Fragment from 'core/fragment';
import Templates from 'core/templates';
export default {
/**
* Reload the question bank UI, retaining the current filters and sort data.
*
* @param {Element} uiRoot The root element of the UI to be refreshed. Must contain "component", "callback" and "contextid" in
* its data attributes, to be passed to the Fragment API.
* @param {URL} returnUrl The url of the current page, containing filter and sort parameters.
* @return {Promise} Resolved when the refresh is complete.
*/
refresh: (uiRoot, returnUrl) => {
return new Promise((resolve, reject) => {
const fragmentData = uiRoot.dataset;
const viewData = {};
const sortData = {};
if (returnUrl) {
returnUrl.searchParams.forEach((value, key) => {
// Match keys like 'sortdata[fieldname]' and convert them to an array,
// because the fragment API doesn't like non-alphanum argument keys.
const sortItem = key.match(/sortdata\[([^\]]+)\]/);
if (sortItem) {
// The item returned by sortItem.pop() is the contents of the matching group, the field name.
sortData[sortItem.pop()] = value;
} else {
viewData[key] = value;
}
});
}
viewData.sortdata = JSON.stringify(sortData);
// We have to use then() there, as loadFragment doesn't appear to work with await.
Fragment.loadFragment(fragmentData.component, fragmentData.callback, fragmentData.contextid, viewData)
.then((html, js) => {
Templates.replaceNode(uiRoot, html, js);
resolve();
return html;
})
.catch(reject);
});
}
};
+98
View File
@@ -0,0 +1,98 @@
<?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/>.
/**
* Actions controller
*
* Perform a synchronous action to modify the question bank UI and redirect back to the previous page.
* These features are mostly progressively enhanced by actions.js and web services, but this remains as a fallback.
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
$action = required_param('action', PARAM_TEXT);
$global = optional_param('global', false, PARAM_BOOL);
$returnurl = optional_param('returnurl', '/question/bank/columnsortorder/sortcolumns.php', PARAM_LOCALURL);
require_login();
if ($global) {
require_capability('moodle/site:config', context_system::instance());
}
if ($action === 'debugreset' && $CFG->debug === DEBUG_DEVELOPER) {
$columnmanager = new \qbank_columnsortorder\column_manager($global);
$columnmanager::set_hidden_columns([], $global);
$columnmanager::set_column_order([], $global);
$columnmanager::set_column_size('', $global);
redirect(new moodle_url($returnurl));
}
require_sesskey();
$columnmanager = new \qbank_columnsortorder\column_manager($global);
switch ($action) {
case 'add':
case 'remove':
$column = required_param('column', PARAM_RAW);
[$columnclass, ] = explode(\core_question\local\bank\column_base::ID_SEPARATOR, $column);
if (!class_exists($columnclass)) {
throw new invalid_parameter_exception("'{$columnclass}' is not a valid column class.");
}
$hiddencolumns = $columnmanager->hiddencolumns;
if ($action === 'add') {
$key = array_search($column, $hiddencolumns);
if ($key !== false) {
unset($hiddencolumns[$key]);
}
} else {
if (!in_array($column, $hiddencolumns)) {
$hiddencolumns[] = $column;
}
}
$columnmanager::set_hidden_columns($hiddencolumns, $global);
break;
case 'savewidths':
$rawwidths = optional_param_array('width', [], PARAM_INT);
$widths = [];
foreach (array_filter($rawwidths) as $escapedclass => $width) {
$class = str_replace('__', '\\', $escapedclass);
// Validate that the class exists and the width is valid.
// Since the browser uses Constraint Validation to prevent the form being submitted with an invalid width,
// the only way we'll get one here is if someone is messing around, so don't worry about re-displaying the
// form with an error message, just ignore the invalid value.
if (class_exists($class) && $width >= 10) {
$widths[] = (object)[
'column' => $class,
'width' => $width,
];
}
}
$columnmanager::set_column_size(json_encode($widths), $global);
break;
case 'reset':
$columnmanager::set_hidden_columns(null, $global);
$columnmanager::set_column_order(null, $global);
$columnmanager::set_column_size(null, $global);
break;
}
redirect(new moodle_url($returnurl));
+11
View File
@@ -0,0 +1,11 @@
define("qbank_columnsortorder/actions",["exports","core/sortable_list","jquery","qbank_columnsortorder/repository","core/notification","core_question/refresh_ui"],(function(_exports,_sortable_list,_jquery,repository,_notification,_refresh_ui){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Common javascript for handling actions on the admin page and the user's view of the question bank.
*
* @module qbank_columnsortorder/actions
* @copyright 2023 onwards Catalyst IT Europe Ltd
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupSortableLists=_exports.setupActionButtons=_exports.getColumnOrder=_exports.SELECTORS=void 0,_sortable_list=_interopRequireDefault(_sortable_list),_jquery=_interopRequireDefault(_jquery),repository=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(repository),_notification=_interopRequireDefault(_notification),_refresh_ui=_interopRequireDefault(_refresh_ui);const SELECTORS={columnList:".qbank-column-list",sortableColumn:".qbank-sortable-column",removeLink:"[data-action=remove]",moveHandler:"[data-drag-type=move]",addColumn:".addcolumn",addLink:"[data-action=add]",actionLink:".action-link"};_exports.SELECTORS=SELECTORS;_exports.setupSortableLists=function(listRoot){let vertical=arguments.length>1&&void 0!==arguments[1]&&arguments[1],global=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const sortableList=new _sortable_list.default(listRoot,{moveHandlerSelector:SELECTORS.moveHandler,isHorizontal:!vertical});sortableList.getElementName=element=>Promise.resolve(element.data("name"));const sortableColumns=(0,_jquery.default)(SELECTORS.sortableColumn);return sortableColumns.on(_sortable_list.default.EVENTS.DROP,(()=>{repository.setColumnbankOrder(getColumnOrder(listRoot),global).catch(_notification.default.exception),listRoot.querySelectorAll(SELECTORS.sortableColumn).forEach((item=>item.classList.remove("active")))})),sortableColumns.on(_sortable_list.default.EVENTS.DRAGSTART,(event=>{event.currentTarget.classList.add("active")})),sortableColumns};_exports.setupActionButtons=function(uiRoot){let global=arguments.length>1&&void 0!==arguments[1]&&arguments[1];uiRoot.addEventListener("click",(async e=>{const actionLink=e.target.closest(SELECTORS.actionLink);if(actionLink)try{e.preventDefault();const action=actionLink.dataset.action;if("add"===action||"remove"===action){const hiddenColumns=[],addColumnList=document.querySelector(SELECTORS.addColumn);addColumnList&&addColumnList.querySelectorAll(SELECTORS.addLink).forEach((item=>{"add"===action&&item===actionLink||hiddenColumns.push(item.dataset.column)})),"remove"===action&&hiddenColumns.push(actionLink.dataset.column),await repository.setHiddenColumns(hiddenColumns,global)}else"reset"===action&&await repository.resetColumns(global);const actionUrl=new URL(actionLink.href),returnUrl=new URL(actionUrl.searchParams.get("returnurl").replaceAll("&amp;","&"));await _refresh_ui.default.refresh(uiRoot,returnUrl)}catch(ex){await _notification.default.exception(ex)}}))};const getColumnOrder=listRoot=>{const columns=Array.from(listRoot.querySelectorAll("[data-columnid]")).map((column=>column.dataset.columnid));return columns.filter(((value,index)=>columns.indexOf(value)===index))};_exports.getColumnOrder=getColumnOrder}));
//# sourceMappingURL=actions.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
define("qbank_columnsortorder/admin_actions",["exports","qbank_columnsortorder/actions","qbank_columnsortorder/repository","core/notification","core/pending"],(function(_exports,actions,repository,_notification,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
/**
* Javascript for handling actions on the admin page
*
* @module qbank_columnsortorder/admin_actions
* @copyright 2023 onwards Catalyst IT Europe Ltd
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,actions=_interopRequireWildcard(actions),repository=_interopRequireWildcard(repository),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);_exports.init=id=>{const uiRoot=document.getElementById(id),listRoot=uiRoot.querySelector(actions.SELECTORS.columnList);actions.setupSortableLists(listRoot,!0,!0),actions.setupActionButtons(uiRoot,!0),(listRoot=>{listRoot.addEventListener("change",(async()=>{const pendingPromise=new _pending.default("saveWidths"),columns=listRoot.querySelectorAll(actions.SELECTORS.sortableColumn),widths=[];columns.forEach((column=>{const widthInput=column.querySelector(".width-input"),valid=widthInput.checkValidity();widthInput.closest(".has-validation").classList.add("was-validated"),valid&&widths.push({column:column.dataset.columnid,width:widthInput.value})})),await repository.setColumnSize(JSON.stringify(widths),!0).catch(_notification.default.exception),pendingPromise.resolve()}))})(listRoot)}}));
//# sourceMappingURL=admin_actions.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"admin_actions.min.js","sources":["../src/admin_actions.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 handling actions on the admin page\n *\n * @module qbank_columnsortorder/admin_actions\n * @copyright 2023 onwards Catalyst IT Europe Ltd\n * @author Mark Johnson <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as actions from 'qbank_columnsortorder/actions';\nimport * as repository from 'qbank_columnsortorder/repository';\nimport Notification from \"core/notification\";\nimport Pending from 'core/pending';\n\n/**\n * Event handler to save the custom column widths when a field is edited.\n *\n * @param {Element} listRoot The root element of the list of columns.\n */\nconst setupSaveWidths = listRoot => {\n listRoot.addEventListener('change', async() => {\n const pendingPromise = new Pending('saveWidths');\n const columns = listRoot.querySelectorAll(actions.SELECTORS.sortableColumn);\n const widths = [];\n columns.forEach(column => {\n const widthInput = column.querySelector('.width-input');\n const valid = widthInput.checkValidity();\n widthInput.closest('.has-validation').classList.add('was-validated');\n if (!valid) {\n return;\n }\n widths.push({\n column: column.dataset.columnid,\n width: widthInput.value,\n });\n });\n await repository.setColumnSize(JSON.stringify(widths), true).catch(Notification.exception);\n pendingPromise.resolve();\n });\n};\n\n/**\n * Initialize module\n *\n * Set up event handlers for the action buttons, width fields and initialise column sorting.\n *\n * @param {String} id ID for the admin UI root element.\n */\nexport const init = id => {\n const uiRoot = document.getElementById(id);\n const listRoot = uiRoot.querySelector(actions.SELECTORS.columnList);\n actions.setupSortableLists(listRoot, true, true);\n actions.setupActionButtons(uiRoot, true);\n setupSaveWidths(listRoot);\n};\n"],"names":["id","uiRoot","document","getElementById","listRoot","querySelector","actions","SELECTORS","columnList","setupSortableLists","setupActionButtons","addEventListener","async","pendingPromise","Pending","columns","querySelectorAll","sortableColumn","widths","forEach","column","widthInput","valid","checkValidity","closest","classList","add","push","dataset","columnid","width","value","repository","setColumnSize","JSON","stringify","catch","Notification","exception","resolve","setupSaveWidths"],"mappings":";;;;;;;;sRA+DoBA,WACVC,OAASC,SAASC,eAAeH,IACjCI,SAAWH,OAAOI,cAAcC,QAAQC,UAAUC,YACxDF,QAAQG,mBAAmBL,UAAU,GAAM,GAC3CE,QAAQI,mBAAmBT,QAAQ,GAjCfG,CAAAA,WACpBA,SAASO,iBAAiB,UAAUC,gBAC1BC,eAAiB,IAAIC,iBAAQ,cAC7BC,QAAUX,SAASY,iBAAiBV,QAAQC,UAAUU,gBACtDC,OAAS,GACfH,QAAQI,SAAQC,eACNC,WAAaD,OAAOf,cAAc,gBAClCiB,MAAQD,WAAWE,gBACzBF,WAAWG,QAAQ,mBAAmBC,UAAUC,IAAI,iBAC/CJ,OAGLJ,OAAOS,KAAK,CACRP,OAAQA,OAAOQ,QAAQC,SACvBC,MAAOT,WAAWU,iBAGpBC,WAAWC,cAAcC,KAAKC,UAAUjB,SAAS,GAAMkB,MAAMC,sBAAaC,WAChFzB,eAAe0B,cAgBnBC,CAAgBpC"}
@@ -0,0 +1,3 @@
define("qbank_columnsortorder/repository",["exports","core/ajax"],(function(_exports,_ajax){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setHiddenColumns=_exports.setColumnbankOrder=_exports.setColumnSize=_exports.resetColumns=void 0;_exports.setHiddenColumns=function(columns){let global=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return(0,_ajax.call)([{methodname:"qbank_columnsortorder_set_hidden_columns",args:{columns:columns,global:global}}])[0]};_exports.setColumnbankOrder=function(columns){let global=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return(0,_ajax.call)([{methodname:"qbank_columnsortorder_set_columnbank_order",args:{columns:columns,global:global}}])[0]};_exports.setColumnSize=function(sizes){let global=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return(0,_ajax.call)([{methodname:"qbank_columnsortorder_set_column_size",args:{sizes:sizes,global:global}}])[0]};_exports.resetColumns=function(){let global=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return Promise.all((0,_ajax.call)([{methodname:"qbank_columnsortorder_set_column_size",args:{global:global}},{methodname:"qbank_columnsortorder_set_columnbank_order",args:{global:global}},{methodname:"qbank_columnsortorder_set_hidden_columns",args:{global:global}}]))}}));
//# sourceMappingURL=repository.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"repository.min.js","sources":["../src/repository.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 * External function calls for qbank_columnsortorder\n *\n * @module qbank_columnsortorder/repository\n * @copyright 2023 Catalyst IT Europe Ltd.\n * @author Mark Johnson <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\n\n/**\n * Save the list of hidden columns\n *\n * @param {String[]} columns List of hidden column names\n * @param {Boolean} global Set global config setting, rather than user preference\n * @return {Promise}\n */\nexport const setHiddenColumns = (columns, global = false) => fetchMany([{\n methodname: 'qbank_columnsortorder_set_hidden_columns',\n args: {\n columns,\n global,\n },\n}])[0];\n\n/**\n * Save the order of columns\n *\n * @param {String[]} columns List of column names in the desired order\n * @param {Boolean} global Set global config setting, rather than user preference\n * @return {Promise}\n */\nexport const setColumnbankOrder = (columns, global = false) => fetchMany([{\n methodname: 'qbank_columnsortorder_set_columnbank_order',\n args: {\n columns,\n global,\n },\n}])[0];\n\n/**\n * Save the column widths\n *\n * @param {String} sizes JSON string encoding an array of objects with \"column\" and \"width\" properties.\n * @param {Boolean} global Set global config setting, rather than user preference\n * @return {Promise}\n */\nexport const setColumnSize = (sizes, global = false) => fetchMany([{\n methodname: 'qbank_columnsortorder_set_column_size',\n args: {\n sizes,\n global,\n },\n}])[0];\n\n/**\n * Reset all settings.\n *\n * @param {Boolean} global Reset global config settings, rather than user preference\n * @return {Promise}\n */\nexport const resetColumns = (global = false) => Promise.all(\n fetchMany([\n {\n methodname: 'qbank_columnsortorder_set_column_size',\n args: {\n global,\n },\n },\n {\n methodname: 'qbank_columnsortorder_set_columnbank_order',\n args: {\n global,\n },\n },\n {\n methodname: 'qbank_columnsortorder_set_hidden_columns',\n args: {\n global,\n },\n },\n ])\n);\n"],"names":["columns","global","methodname","args","sizes","Promise","all"],"mappings":"wRAiCgC,SAACA,aAASC,sEAAmB,cAAU,CAAC,CACpEC,WAAY,2CACZC,KAAM,CACFH,QAAAA,QACAC,OAAAA,WAEJ,gCAS8B,SAACD,aAASC,sEAAmB,cAAU,CAAC,CACtEC,WAAY,6CACZC,KAAM,CACFH,QAAAA,QACAC,OAAAA,WAEJ,2BASyB,SAACG,WAAOH,sEAAmB,cAAU,CAAC,CAC/DC,WAAY,wCACZC,KAAM,CACFC,MAAAA,MACAH,OAAAA,WAEJ,0BAQwB,eAACA,sEAAmBI,QAAQC,KACpD,cAAU,CACN,CACIJ,WAAY,wCACZC,KAAM,CACFF,OAAAA,SAGR,CACIC,WAAY,6CACZC,KAAM,CACFF,OAAAA,SAGR,CACIC,WAAY,2CACZC,KAAM,CACFF,OAAAA"}
@@ -1,10 +0,0 @@
define("qbank_columnsortorder/sort_columns",["exports","core/ajax","core/notification","core/sortable_list","jquery"],(function(_exports,_ajax,_notification,_sortable_list,_jquery){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Javascript for sorting columns in question bank view.
*
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Ghaly Marc-Alexandre <marc-alexandreghaly@catalyst-ca.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_sortable_list=_interopRequireDefault(_sortable_list),_jquery=_interopRequireDefault(_jquery);const setOrder=columns=>(0,_ajax.call)([{methodname:"qbank_columnsortorder_set_columnbank_order",args:{columns:columns}}])[0],getColumnOrder=listRoot=>{const columns=Array.from(listRoot.querySelectorAll("[data-pluginname]")).map((column=>column.dataset.pluginname));return columns.filter(((value,index)=>columns.indexOf(value)===index))};_exports.init=id=>{(listRoot=>{new _sortable_list.default(".list",{moveHandlerSelector:".item"}),(0,_jquery.default)(".item").on(_sortable_list.default.EVENTS.DROP,(()=>{const columns=getColumnOrder(listRoot);setOrder(columns).catch(_notification.exception),listRoot.querySelectorAll(".item").forEach((item=>item.classList.remove("active")))})),(0,_jquery.default)(".item").on(_sortable_list.default.EVENTS.DRAGSTART,(event=>{event.currentTarget.classList.add("active")}))})(document.querySelector("#".concat(id)))}}));
//# sourceMappingURL=sort_columns.min.js.map
@@ -1 +0,0 @@
{"version":3,"file":"sort_columns.min.js","sources":["../src/sort_columns.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 sorting columns in question bank view.\n *\n * @copyright 2021 Catalyst IT Australia Pty Ltd\n * @author Ghaly Marc-Alexandre <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {exception as displayException} from 'core/notification';\nimport SortableList from 'core/sortable_list';\nimport jQuery from 'jquery';\n\n/**\n * Sets up sortable list in the column sort order page.\n * @param {Element} listRoot\n */\nconst setupSortableLists = (listRoot) => {\n new SortableList('.list', {\n moveHandlerSelector: '.item',\n });\n\n jQuery('.item').on(SortableList.EVENTS.DROP, () => {\n const columns = getColumnOrder(listRoot);\n setOrder(columns).catch(displayException);\n listRoot.querySelectorAll('.item').forEach(item => item.classList.remove('active'));\n });\n\n jQuery('.item').on(SortableList.EVENTS.DRAGSTART, (event) => {\n event.currentTarget.classList.add('active');\n });\n};\n\n/**\n * Call external function set_order - inserts the updated column in the config_plugins table.\n *\n * @param {String} columns String that contains column order.\n * @returns {Promise}\n */\nconst setOrder = columns => fetchMany([{\n methodname: 'qbank_columnsortorder_set_columnbank_order',\n args: {columns},\n}])[0];\n\n/**\n * Gets the newly reordered columns to display in the question bank view.\n * @param {Element} listRoot\n * @returns {Array}\n */\nconst getColumnOrder = listRoot => {\n const columns = Array.from(listRoot.querySelectorAll('[data-pluginname]'))\n .map(column => column.dataset.pluginname);\n\n return columns.filter((value, index) => columns.indexOf(value) === index);\n};\n\n/**\n * Initialize module\n * @param {String} id unique id for columns.\n */\nexport const init = id => {\n const listRoot = document.querySelector(`#${id}`);\n setupSortableLists(listRoot);\n};\n"],"names":["setOrder","columns","methodname","args","getColumnOrder","listRoot","Array","from","querySelectorAll","map","column","dataset","pluginname","filter","value","index","indexOf","id","SortableList","moveHandlerSelector","on","EVENTS","DROP","catch","displayException","forEach","item","classList","remove","DRAGSTART","event","currentTarget","add","setupSortableLists","document","querySelector"],"mappings":";;;;;;;sLAsDMA,SAAWC,UAAW,cAAU,CAAC,CACnCC,WAAY,6CACZC,KAAM,CAACF,QAAAA,YACP,GAOEG,eAAiBC,iBACbJ,QAAUK,MAAMC,KAAKF,SAASG,iBAAiB,sBAChDC,KAAIC,QAAUA,OAAOC,QAAQC,oBAE3BX,QAAQY,QAAO,CAACC,MAAOC,QAAUd,QAAQe,QAAQF,SAAWC,uBAOnDE,KA3CQZ,CAAAA,eACpBa,uBAAa,QAAS,CACtBC,oBAAqB,8BAGlB,SAASC,GAAGF,uBAAaG,OAAOC,MAAM,WACnCrB,QAAUG,eAAeC,UAC/BL,SAASC,SAASsB,MAAMC,yBACxBnB,SAASG,iBAAiB,SAASiB,SAAQC,MAAQA,KAAKC,UAAUC,OAAO,mCAGtE,SAASR,GAAGF,uBAAaG,OAAOQ,WAAYC,QAC/CA,MAAMC,cAAcJ,UAAUK,IAAI,cAiCtCC,CADiBC,SAASC,yBAAkBlB"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,125 @@
// 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/>.
/**
* Common javascript for handling actions on the admin page and the user's view of the question bank.
*
* @module qbank_columnsortorder/actions
* @copyright 2023 onwards Catalyst IT Europe Ltd
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import SortableList from 'core/sortable_list';
import $ from 'jquery';
import * as repository from 'qbank_columnsortorder/repository';
import Notification from "core/notification";
import RefreshUi from 'core_question/refresh_ui';
export const SELECTORS = {
columnList: '.qbank-column-list',
sortableColumn: '.qbank-sortable-column',
removeLink: '[data-action=remove]',
moveHandler: '[data-drag-type=move]',
addColumn: '.addcolumn',
addLink: '[data-action=add]',
actionLink: '.action-link',
};
/**
* Sets up sortable list in the column sort order page.
*
* @param {Element} listRoot Element containing the sortable list.
* @param {Boolean} vertical Is the list in vertical orientation, rather than horizonal?
* @param {Boolean} global Should changes be saved to global config, rather than user preferences?
* @return {jQuery} sortable column elements, for attaching additional event listeners.
*/
export const setupSortableLists = (listRoot, vertical = false, global = false) => {
const sortableList = new SortableList(listRoot, {
moveHandlerSelector: SELECTORS.moveHandler,
isHorizontal: !vertical,
});
sortableList.getElementName = element => Promise.resolve(element.data('name'));
const sortableColumns = $(SELECTORS.sortableColumn);
sortableColumns.on(SortableList.EVENTS.DROP, () => {
repository.setColumnbankOrder(getColumnOrder(listRoot), global).catch(Notification.exception);
listRoot.querySelectorAll(SELECTORS.sortableColumn).forEach(item => item.classList.remove('active'));
});
sortableColumns.on(SortableList.EVENTS.DRAGSTART, (event) => {
event.currentTarget.classList.add('active');
});
return sortableColumns;
};
/**
* Set up event handlers for action buttons.
*
* For each action, call the web service to update the appropriate setting or user preference, then call the fragment to
* refresh the view.
*
* @param {Element} uiRoot The root of the question bank UI.
* @param {Boolean} global Should changes be saved to global config, rather than user preferences?
*/
export const setupActionButtons = (uiRoot, global = false) => {
uiRoot.addEventListener('click', async(e) => {
const actionLink = e.target.closest(SELECTORS.actionLink);
if (!actionLink) {
return;
}
try {
e.preventDefault();
const action = actionLink.dataset.action;
if (action === 'add' || action === 'remove') {
const hiddenColumns = [];
const addColumnList = document.querySelector(SELECTORS.addColumn);
if (addColumnList) {
addColumnList.querySelectorAll(SELECTORS.addLink).forEach(item => {
if (action === 'add' && item === actionLink) {
return;
}
hiddenColumns.push(item.dataset.column);
});
}
if (action === 'remove') {
hiddenColumns.push(actionLink.dataset.column);
}
await repository.setHiddenColumns(hiddenColumns, global);
} else if (action === 'reset') {
await repository.resetColumns(global);
}
const actionUrl = new URL(actionLink.href);
const returnUrl = new URL(actionUrl.searchParams.get('returnurl').replaceAll('&amp;', '&'));
await RefreshUi.refresh(uiRoot, returnUrl);
} catch (ex) {
await Notification.exception(ex);
}
});
};
/**
* Gets the newly reordered columns to display in the question bank view.
* @param {Element} listRoot
* @returns {Array}
*/
export const getColumnOrder = listRoot => {
const columns = Array.from(listRoot.querySelectorAll('[data-columnid]'))
.map(column => column.dataset.columnid);
return columns.filter((value, index) => columns.indexOf(value) === index);
};
@@ -0,0 +1,70 @@
// 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 handling actions on the admin page
*
* @module qbank_columnsortorder/admin_actions
* @copyright 2023 onwards Catalyst IT Europe Ltd
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as actions from 'qbank_columnsortorder/actions';
import * as repository from 'qbank_columnsortorder/repository';
import Notification from "core/notification";
import Pending from 'core/pending';
/**
* Event handler to save the custom column widths when a field is edited.
*
* @param {Element} listRoot The root element of the list of columns.
*/
const setupSaveWidths = listRoot => {
listRoot.addEventListener('change', async() => {
const pendingPromise = new Pending('saveWidths');
const columns = listRoot.querySelectorAll(actions.SELECTORS.sortableColumn);
const widths = [];
columns.forEach(column => {
const widthInput = column.querySelector('.width-input');
const valid = widthInput.checkValidity();
widthInput.closest('.has-validation').classList.add('was-validated');
if (!valid) {
return;
}
widths.push({
column: column.dataset.columnid,
width: widthInput.value,
});
});
await repository.setColumnSize(JSON.stringify(widths), true).catch(Notification.exception);
pendingPromise.resolve();
});
};
/**
* Initialize module
*
* Set up event handlers for the action buttons, width fields and initialise column sorting.
*
* @param {String} id ID for the admin UI root element.
*/
export const init = id => {
const uiRoot = document.getElementById(id);
const listRoot = uiRoot.querySelector(actions.SELECTORS.columnList);
actions.setupSortableLists(listRoot, true, true);
actions.setupActionButtons(uiRoot, true);
setupSaveWidths(listRoot);
};
@@ -0,0 +1,99 @@
// 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/>.
/**
* External function calls for qbank_columnsortorder
*
* @module qbank_columnsortorder/repository
* @copyright 2023 Catalyst IT Europe Ltd.
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call as fetchMany} from 'core/ajax';
/**
* Save the list of hidden columns
*
* @param {String[]} columns List of hidden column names
* @param {Boolean} global Set global config setting, rather than user preference
* @return {Promise}
*/
export const setHiddenColumns = (columns, global = false) => fetchMany([{
methodname: 'qbank_columnsortorder_set_hidden_columns',
args: {
columns,
global,
},
}])[0];
/**
* Save the order of columns
*
* @param {String[]} columns List of column names in the desired order
* @param {Boolean} global Set global config setting, rather than user preference
* @return {Promise}
*/
export const setColumnbankOrder = (columns, global = false) => fetchMany([{
methodname: 'qbank_columnsortorder_set_columnbank_order',
args: {
columns,
global,
},
}])[0];
/**
* Save the column widths
*
* @param {String} sizes JSON string encoding an array of objects with "column" and "width" properties.
* @param {Boolean} global Set global config setting, rather than user preference
* @return {Promise}
*/
export const setColumnSize = (sizes, global = false) => fetchMany([{
methodname: 'qbank_columnsortorder_set_column_size',
args: {
sizes,
global,
},
}])[0];
/**
* Reset all settings.
*
* @param {Boolean} global Reset global config settings, rather than user preference
* @return {Promise}
*/
export const resetColumns = (global = false) => Promise.all(
fetchMany([
{
methodname: 'qbank_columnsortorder_set_column_size',
args: {
global,
},
},
{
methodname: 'qbank_columnsortorder_set_columnbank_order',
args: {
global,
},
},
{
methodname: 'qbank_columnsortorder_set_hidden_columns',
args: {
global,
},
},
])
);
@@ -1,79 +0,0 @@
// 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 sorting columns in question bank view.
*
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Ghaly Marc-Alexandre <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call as fetchMany} from 'core/ajax';
import {exception as displayException} from 'core/notification';
import SortableList from 'core/sortable_list';
import jQuery from 'jquery';
/**
* Sets up sortable list in the column sort order page.
* @param {Element} listRoot
*/
const setupSortableLists = (listRoot) => {
new SortableList('.list', {
moveHandlerSelector: '.item',
});
jQuery('.item').on(SortableList.EVENTS.DROP, () => {
const columns = getColumnOrder(listRoot);
setOrder(columns).catch(displayException);
listRoot.querySelectorAll('.item').forEach(item => item.classList.remove('active'));
});
jQuery('.item').on(SortableList.EVENTS.DRAGSTART, (event) => {
event.currentTarget.classList.add('active');
});
};
/**
* Call external function set_order - inserts the updated column in the config_plugins table.
*
* @param {String} columns String that contains column order.
* @returns {Promise}
*/
const setOrder = columns => fetchMany([{
methodname: 'qbank_columnsortorder_set_columnbank_order',
args: {columns},
}])[0];
/**
* Gets the newly reordered columns to display in the question bank view.
* @param {Element} listRoot
* @returns {Array}
*/
const getColumnOrder = listRoot => {
const columns = Array.from(listRoot.querySelectorAll('[data-pluginname]'))
.map(column => column.dataset.pluginname);
return columns.filter((value, index) => columns.indexOf(value) === index);
};
/**
* Initialize module
* @param {String} id unique id for columns.
*/
export const init = id => {
const listRoot = document.querySelector(`#${id}`);
setupSortableLists(listRoot);
};
@@ -0,0 +1,366 @@
// 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 customising the user's view of the question bank
*
* @module qbank_columnsortorder/user_actions
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Ghaly Marc-Alexandre <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as actions from 'qbank_columnsortorder/actions';
import * as repository from 'qbank_columnsortorder/repository';
import {get_string as getString} from 'core/str';
import ModalEvents from 'core/modal_events';
import ModalSaveCancel from 'core/modal_save_cancel';
import Notification from "core/notification";
import SortableList from 'core/sortable_list';
import Templates from "core/templates";
const SELECTORS = {
uiRoot: '.questionbankwindow',
moveAction: '.menu-action[data-action=move]',
resizeAction: '.menu-action[data-action=resize]',
resizeHandle: '.qbank_columnsortorder-action-handle.resize',
handleContainer: '.handle-container',
headerContainer: '.header-container',
tableColumn: identifier => `td[data-columnid="${identifier.replace(/["\\]/g, '\\$&')}"]`,
};
/** To track mouse event on a table header */
let currentHeader;
/** Current mouse x postion, to track mouse event on a table header */
let currentX;
/**
* Flag to temporarily prevent move and resize handles from being shown or hidden.
*
* @type {boolean}
*/
let suspendShowHideHandles = false;
/**
* Add handle containers for move and resize handles.
*
* @param {Element} uiRoot The root element of the quesiton bank UI.
* @return {Promise} Resolved after the containers have been added to each column header.
*/
const addHandleContainers = uiRoot => {
return new Promise((resolve) => {
const headerContainers = uiRoot.querySelectorAll(SELECTORS.headerContainer);
Templates.renderForPromise('qbank_columnsortorder/handle_container', {})
.then(({html, js}) => {
headerContainers.forEach(container => {
Templates.prependNodeContents(container, html, js);
});
resolve();
return headerContainers;
}).catch(Notification.exception);
});
};
/**
* Render move handles in each container.
*
* This takes a list of the move actions rendered in each column header, and creates a corresponding drag handle for each.
*
* @param {NodeList} moveActions Menu actions for moving columns.
*/
const setUpMoveHandles = moveActions => {
moveActions.forEach(moveAction => {
const header = moveAction.closest('th');
header.classList.add('qbank-sortable-column');
const handleContainer = header.querySelector(SELECTORS.handleContainer);
const context = {
action: "move",
dragtype: "move",
target: '',
title: moveAction.title,
pixicon: "i/dragdrop",
pixcomponent: "core",
popup: true
};
return Templates.renderForPromise('qbank_columnsortorder/action_handle', context)
.then(({html, js}) => {
Templates.prependNodeContents(handleContainer, html, js);
return handleContainer;
}).catch(Notification.exception);
});
};
/**
* Serialise the current column sizes.
*
* This finds the current width set in each column header's style property, and returns them encoded as a JSON string.
*
* @param {Element} uiRoot The root element of the quesiton bank UI.
* @return {String} JSON array containing a list of objects with column and width properties.
*/
const serialiseColumnSizes = (uiRoot) => {
const columnSizes = [];
const tableHeaders = uiRoot.querySelectorAll('th');
tableHeaders.forEach(header => {
// Only get the width set via style attribute (set by move action).
const width = parseInt(header.style.width);
if (!width || isNaN(width)) {
return;
}
columnSizes.push({
column: header.dataset.columnid,
width: width
});
});
return JSON.stringify(columnSizes);
};
/**
* Render resize handles in each container.
*
* This takes a list of the resize actions rendered in each column header, and creates a corresponding drag handle for each.
* It also initialises the event handlers for the drag handles and resize modal.
*
* @param {Element} uiRoot Question bank UI root element.
*/
const setUpResizeHandles = (uiRoot) => {
const resizeActions = uiRoot.querySelectorAll(SELECTORS.resizeAction);
resizeActions.forEach(resizeAction => {
const headerContainer = resizeAction.closest(SELECTORS.headerContainer);
const handleContainer = headerContainer.querySelector(SELECTORS.handleContainer);
const context = {
action: "resize",
target: '',
title: resizeAction.title,
pixicon: 'i/twoway',
pixcomponent: 'core',
popup: true
};
return Templates.renderForPromise('qbank_columnsortorder/action_handle', context)
.then(({html, js}) => {
Templates.appendNodeContents(handleContainer, html, js);
return handleContainer;
}).catch(Notification.exception);
});
let moveTracker = false;
let currentResizeHandle = null;
// Start mouse event on headers.
uiRoot.addEventListener('mousedown', e => {
currentResizeHandle = e.target.closest(SELECTORS.resizeHandle);
// Return if it is not ' resize' button.
if (!currentResizeHandle) {
return;
}
// Save current position.
currentX = e.pageX;
// Find the header.
currentHeader = e.target.closest(actions.SELECTORS.sortableColumn);
moveTracker = false;
suspendShowHideHandles = true;
});
// Resize column as the mouse move.
document.addEventListener('mousemove', e => {
if (!currentHeader || !currentResizeHandle || currentX === 0) {
return;
}
// Prevent text selection as the handle is dragged.
document.getSelection().removeAllRanges();
// Adjust the column width according the amount the handle was dragged.
const offset = e.pageX - currentX;
currentX = e.pageX;
const newWidth = currentHeader.offsetWidth + offset;
currentHeader.style.width = newWidth + 'px';
moveTracker = true;
});
// Set new size when mouse is up.
document.addEventListener('mouseup', () => {
if (!currentHeader || !currentResizeHandle || currentX === 0) {
return;
}
if (moveTracker) {
// If the mouse moved, we are changing the size by drag, so save the change.
repository.setColumnSize(serialiseColumnSizes(uiRoot)).catch(Notification.exception);
} else {
// If the mouse didn't move, display a modal to change the size using a form.
showResizeModal(currentHeader, uiRoot);
}
currentHeader = null;
currentResizeHandle = null;
currentX = 0;
moveTracker = false;
suspendShowHideHandles = false;
});
};
/**
* Event handler for resize actions in each column header.
*
* This will listen for a click on any resize action, and activate the corresponding resize modal.
*
* @param {Element} uiRoot Question bank UI root element.
*/
const setUpResizeActions = uiRoot => {
uiRoot.addEventListener('click', (e) => {
const resizeAction = e.target.closest(SELECTORS.resizeAction);
if (resizeAction) {
e.preventDefault();
const currentHeader = resizeAction.closest('th');
showResizeModal(currentHeader, uiRoot);
}
});
};
/**
* Show a modal containing a number input for changing a column width without click-and-drag.
*
* @param {Element} currentHeader The header element that is being resized.
* @param {Element} uiRoot The question bank UI root element.
* @returns {Promise<void>}
*/
const showResizeModal = async(currentHeader, uiRoot) => {
const initialWidth = currentHeader.offsetWidth;
const modal = await ModalSaveCancel.create({
title: getString('resizecolumn', 'qbank_columnsortorder', currentHeader.textContent),
body: Templates.render('qbank_columnsortorder/resize_modal', {}),
show: true,
});
const root = modal.getRoot();
root.on(ModalEvents.cancel, () => {
currentHeader.style.width = `${initialWidth}px`;
});
root.on(ModalEvents.save, () => {
repository.setColumnSize(serialiseColumnSizes(uiRoot)).catch(Notification.exception);
});
const body = await modal.bodyPromise;
const input = body.get(0).querySelector('input');
input.value = initialWidth;
input.addEventListener('change', e => {
const newWidth = e.target.value;
currentHeader.style.width = `${newWidth}px`;
});
};
/**
* Event handler for move actions in each column header.
*
* This will listen for a click on any move action, pass the click to the corresponding move handle, causing its modal to be shown.
*
* @param {Element} uiRoot Question bank UI root element.
*/
const setUpMoveActions = uiRoot => {
uiRoot.addEventListener('click', e => {
const moveAction = e.target.closest(SELECTORS.moveAction);
if (moveAction) {
e.preventDefault();
const sortableColumn = moveAction.closest(actions.SELECTORS.sortableColumn);
const moveHandle = sortableColumn.querySelector(actions.SELECTORS.moveHandler);
moveHandle.click();
}
});
};
/**
* Event handler for showing and hiding handles when the mouse is over a column header.
*
* Implementing this behaviour using the :hover CSS pseudoclass is not sufficient, as the mouse may move over the neighbouring
* header while dragging, leading to some odd behaviour. This allows us to suspend the show/hide behaviour while a handle is being
* dragged, and so keep the active handle visible until the drag is finished.
*
* @param {Element} uiRoot Question bank UI root element.
*/
const setupShowHideHandles = uiRoot => {
let shownHeader = null;
let tableHead = uiRoot.querySelector('thead');
uiRoot.addEventListener('mouseover', e => {
if (suspendShowHideHandles) {
return;
}
const header = e.target.closest(actions.SELECTORS.sortableColumn);
if (!header && !shownHeader) {
return;
}
if (!header || header !== shownHeader) {
tableHead.querySelector('.show-handles')?.classList.remove('show-handles');
shownHeader = header;
if (header) {
header.classList.add('show-handles');
}
}
});
};
/**
* Event handler for sortable list DROP event.
*
* Find all table cells corresponding to the column of the dropped header, and move them to the new position.
*
* @param {Event} event
*/
const reorderColumns = event => {
// Current header.
const header = event.target;
// Find the previous sibling of the header, which will be used when moving columns.
const insertAfter = header.previousElementSibling;
// Move columns.
const uiRoot = document.querySelector(SELECTORS.uiRoot);
const columns = uiRoot.querySelectorAll(SELECTORS.tableColumn(header.dataset.columnid));
columns.forEach(column => {
const row = column.parentElement;
if (insertAfter) {
// Find the column to insert after.
const insertAfterColumn = row.querySelector(SELECTORS.tableColumn(insertAfter.dataset.columnid));
// Insert the column.
insertAfterColumn.after(column);
} else {
// Insert as the first child (first column in the table).
row.insertBefore(column, row.firstChild);
}
});
};
/**
* Initialize module
*
* Add containers for the drag handles to each column header, then render handles, enable show/hide behaviour, set up drag/drop
* column sorting, then enable the move and resize modals to be triggered from menu actions.
*/
export const init = async() => {
const uiRoot = document.getElementById('questionscontainer');
await addHandleContainers(uiRoot);
setUpMoveHandles(uiRoot.querySelectorAll(SELECTORS.moveAction));
setUpResizeHandles(uiRoot);
setupShowHideHandles(uiRoot);
const sortableColumns = actions.setupSortableLists(uiRoot.querySelector(actions.SELECTORS.columnList));
sortableColumns.on(SortableList.EVENTS.DROP, reorderColumns);
sortableColumns.on(SortableList.EVENTS.DRAGSTART, () => {
suspendShowHideHandles = true;
});
sortableColumns.on(SortableList.EVENTS.DRAGEND, () => {
suspendShowHideHandles = false;
});
setUpMoveActions(uiRoot);
setUpResizeActions(uiRoot);
actions.setupActionButtons(uiRoot);
};
@@ -21,8 +21,14 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/questionlib.php');
use context_system;
use core_question\local\bank\column_base;
use core_question\local\bank\column_manager_base;
use core_question\local\bank\question_edit_contexts;
use core_question\local\bank\view;
use qbank_columnsortorder\local\bank\column_action_move;
use qbank_columnsortorder\local\bank\column_action_remove;
use qbank_columnsortorder\local\bank\column_action_resize;
use qbank_columnsortorder\local\bank\preview_view;
use moodle_url;
/**
@@ -33,40 +39,130 @@ use moodle_url;
* @author Ghaly Marc-Alexandre <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_manager {
class column_manager extends column_manager_base {
/**
* @var array|bool Column order as set in config_plugins 'class' => 'position', ie: question_type_column => 3.
* @var array Column order as set in config_plugins 'class' => 'position', ie: question_type_column => 3.
*/
public $columnorder;
/**
* @var array|bool Disabled columns in config_plugins table.
* @var array hidden columns.
*/
public $hiddencolumns;
/**
* @var array columns with size.
*/
public $colsize;
/**
* @var array Disabled columns in config_plugins table.
*/
public $disabledcolumns;
/**
* Constructor for column_manager class.
*
* @param bool $globalsettings Only use the global default settings, ignoring user preferences?
*/
public function __construct() {
$this->columnorder = get_config('qbank_columnsortorder', 'enabledcol');
$this->disabledcolumns = get_config('qbank_columnsortorder', 'disabledcol');
public function __construct(bool $globalsettings = false) {
$this->columnorder = $this->setup_property('enabledcol', $globalsettings);
if (empty($this->columnorder)) {
$this->columnorder = [
'core_question\local\bank\checkbox_column' . column_base::ID_SEPARATOR . 'checkbox_column',
'qbank_viewquestiontype\question_type_column' . column_base::ID_SEPARATOR . 'question_type_column',
'qbank_viewquestionname\question_name_idnumber_tags_column' . column_base::ID_SEPARATOR .
'question_name_idnumber_tags_column',
'core_question\local\bank\edit_menu_column' . column_base::ID_SEPARATOR . 'edit_menu_column',
'qbank_editquestion\question_status_column' . column_base::ID_SEPARATOR . 'question_status_column',
'qbank_history\version_number_column' . column_base::ID_SEPARATOR . 'version_number_column',
'qbank_viewcreator\creator_name_column' . column_base::ID_SEPARATOR . 'creator_name_column',
'qbank_comment\comment_count_column' . column_base::ID_SEPARATOR . 'comment_count_column',
];
}
$this->hiddencolumns = $this->setup_property('hiddencols', $globalsettings);
$this->colsize = $this->setup_property('colsize', $globalsettings, 'json');
$this->disabledcolumns = $this->setup_property('disabledcol', true); // No user preference for disabledcol.
if ($this->columnorder) {
$this->columnorder = array_flip(explode(',', $this->columnorder));
$this->columnorder = array_flip($this->columnorder);
}
if ($this->disabledcolumns) {
$this->disabledcolumns = array_flip(explode(',', $this->disabledcolumns));
$this->disabledcolumns = array_flip($this->disabledcolumns);
}
}
/**
* Return the value for the given property, based the saved user preference or config setting.
*
* If no value is currently stored, returns an empty array.
*
* @param string $setting The identifier used for the saved config and user preference settings.
* @param bool $global Only get the global default, ignoring the user preference?
* @param string $encoding The encoding used to store the property - csv or json
* @return array
*/
private function setup_property(string $setting, bool $global = false, $encoding = 'csv'): array {
$value = get_config('qbank_columnsortorder', $setting);
if (!$global) {
$value = get_user_preferences("qbank_columnsortorder_{$setting}", $value);
}
if (empty($value)) {
return [];
}
return $encoding == 'csv' ? explode(',', $value) : json_decode($value);
}
/**
* Sets column order in the qbank_columnsortorder plugin config.
*
* @param array $columns Column order to set.
* @param ?array $columns Column order to set. Null value clears the setting.
* @param bool $global save this as a global default, rather than a user preference?
*/
public static function set_column_order(array $columns) : void {
$columns = implode(',', $columns);
set_config('enabledcol', $columns, 'qbank_columnsortorder');
public static function set_column_order(?array $columns, bool $global = false) : void {
if (!is_null($columns)) {
$columns = implode(',', $columns);
}
self::save_preference('enabledcol', $columns, $global);
}
/**
* Hidden Columns.
*
* @param ?array $columns List of hidden columns. Null value clears the setting.
* @param bool $global save this as a global default, rather than a user preference?
*/
public static function set_hidden_columns(?array $columns, bool $global = false) : void {
if (!is_null($columns)) {
$columns = implode(',', $columns);
}
self::save_preference('hiddencols', $columns, $global);
}
/**
* Column size.
*
* @param ?string $sizes columns with width. Null value clears the setting.
* @param bool $global save this as a global default, rather than a user preference?
*/
public static function set_column_size(?string $sizes, bool $global = false) : void {
self::save_preference('colsize', $sizes, $global);
}
/**
* Save Preferences.
*
* @param string $name name of a configuration
* @param ?string $value value of a configuration. Null value clears the setting.
* @param bool $global save this as a global default, rather than a user preference?
*/
private static function save_preference(string $name, ?string $value, bool $global = false): void {
if ($global) {
require_capability('moodle/site:config', context_system::instance());
set_config($name, $value, 'qbank_columnsortorder');
} else {
set_user_preference("qbank_columnsortorder_{$name}", $value);
}
}
/**
@@ -74,12 +170,20 @@ class column_manager {
*
* @return view
*/
protected function get_questionbank(): view {
public function get_questionbank(): view {
$course = (object) ['id' => 0];
$context = context_system::instance();
$contexts = new question_edit_contexts($context);
$category = question_make_default_categories($contexts->all());
$params = ['cat' => $category->id . ',' . $context->id];
// Dummy call to get the objects without error.
$questionbank = new view($contexts, new moodle_url('/question/dummyurl.php'), $course, null);
$questionbank = new preview_view(
$contexts,
new moodle_url('/question/bank/columnsortorder/sortcolumns.php'),
$course,
null,
$params
);
return $questionbank;
}
@@ -94,11 +198,11 @@ class column_manager {
if ($column->get_name() === 'checkbox') {
continue;
}
$classelements = explode('\\', $key);
$columns[] = (object) [
'class' => get_class($column),
'name' => $column->get_title(),
'colname' => end($classelements),
'colname' => $column->get_column_name(),
'id' => $column->get_column_id(),
];
}
return $columns;
@@ -112,20 +216,12 @@ class column_manager {
public function get_disabled_columns(): array {
$disabled = [];
if ($this->disabledcolumns) {
foreach ($this->disabledcolumns as $class => $value) {
if (strpos($class, 'qbank_customfields\custom_field_column') !== false) {
$class = explode('\\', $class);
$disabledname = array_pop($class);
$class = implode('\\', $class);
$disabled[] = (object) [
'disabledname' => $disabledname,
];
} else {
$columnobject = new $class($this->get_questionbank());
$disabled[] = (object) [
'disabledname' => $columnobject->get_title(),
];
}
foreach (array_keys($this->disabledcolumns) as $column) {
[$classname, $columnname] = explode(column_base::ID_SEPARATOR, $column, 2);
$columnobject = $classname::from_column_name($this->get_questionbank(), $columnname);
$disabled[$column] = (object) [
'disabledname' => $columnobject->get_title(),
];
}
}
return $disabled;
@@ -192,17 +288,10 @@ class column_manager {
}
foreach ($allcolumns as $column) {
if (strpos($column->class, $plugin) !== false) {
if ($column->class === 'qbank_customfields\custom_field_column') {
$disabledcolumns[$column->class . '\\' . $column->colname] = $column->class . '\\' . $column->colname;
if (isset($enabledcolumns[$column->class . '\\' . $column->colname])) {
unset($enabledcolumns[$column->class. '\\' . $column->colname]);
}
} else {
$disabledcolumns[$column->class] = $column->class;
if (isset($enabledcolumns[$column->class])) {
unset($enabledcolumns[$column->class]);
}
if (str_contains($column->class, $plugin)) {
$disabledcolumns[$column->id] = $column->id;
if (isset($enabledcolumns[$column->id])) {
unset($enabledcolumns[$column->id]);
}
}
}
@@ -212,7 +301,7 @@ class column_manager {
/**
* Orders columns in the question bank view according to config_plugins table 'qbank_columnsortorder' config.
*
* @param array $ordertosort Unordered array of columns
* @param array $ordertosort Unordered array of columns, [columnname => class]
* @return array $properorder|$ordertosort Returns array ordered if 'qbank_columnsortorder' config exists.
*/
public function get_sorted_columns($ordertosort): array {
@@ -222,32 +311,84 @@ class column_manager {
$columnsortorder = $this->columnorder;
asort($columnsortorder);
$columnorder = [];
foreach ($columnsortorder as $classname => $colposition) {
$colname = explode('\\', $classname);
if (strpos($classname, 'qbank_customfields\custom_field_column') !== false) {
unset($colname[0]);
$classname = implode('\\', $colname);
// Checks if custom column still exists.
if (array_key_exists($classname, $ordertosort)) {
$columnorder[$classname] = $colposition;
} else {
$configtounset = str_replace('\\', '\\\\', $classname);
// Cleans config db.
unset_config($configtounset, 'column_sortorder');
}
} else {
$columnorder[end($colname)] = $colposition;
foreach ($columnsortorder as $columnid => $colposition) {
if (array_key_exists($columnid, $ordertosort)) {
$columnorder[$columnid] = $colposition;
}
}
$properorder = array_merge($columnorder, $ordertosort);
// Always have the checkbox at first column position.
if (isset($properorder['checkbox_column'])) {
$checkboxfirstelement = $properorder['checkbox_column'];
unset($properorder['checkbox_column']);
$properorder = array_merge(['checkbox_column' => $checkboxfirstelement], $properorder);
$checkboxid = 'core_question\local\bank\checkbox_column' . column_base::ID_SEPARATOR . 'checkbox_column';
if (isset($properorder[$checkboxid])) {
$checkboxfirstelement = $properorder[$checkboxid];
unset($properorder[$checkboxid]);
$properorder = array_merge([
$checkboxid => $checkboxfirstelement
], $properorder);
}
return $properorder;
}
return $ordertosort;
}
/**
* Given an array of columns, set the isvisible attribute according to $this->hiddencolumns and $this->disabledcolumns.
*
* @param column_base[] $columns
* @return array
*/
public function set_columns_visibility(array $columns): array {
foreach ($columns as $column) {
if (!is_object($column)) {
continue;
}
$columnid = $column->get_column_id();
$column->isvisible = !in_array($columnid, $this->hiddencolumns) && !array_key_exists($columnid, $this->disabledcolumns);
}
return $columns;
}
/**
* Return $this->colsize mapped as an array of column name => width, excluding empty sizes.
*
* @return array
*/
public function get_colsize_map(): array {
$sizes = array_reduce($this->colsize, function($result, $colsize) {
$result[$colsize->column] = $colsize->width;
return $result;
}, []);
return array_filter($sizes);
}
/**
* Return an array of hidden columns as an array of class => column name
*
* @return array
*/
public function get_hidden_columns(): array {
return array_reduce($this->hiddencolumns, function($result, $hiddencolumn) {
[$columnclass, $columnname] = explode(column_base::ID_SEPARATOR, $hiddencolumn, 2);
$result[$hiddencolumn] = $columnclass::from_column_name($this->get_questionbank(), $columnname)->get_title();
return $result;
}, []);
}
public function get_column_width(column_base $column): string {
$colsizemap = $this->get_colsize_map();
$columnid = $column->get_column_id();
if (array_key_exists($columnid, $colsizemap)) {
return $colsizemap[$columnid] . 'px';
}
return parent::get_column_width($column);
}
public function get_column_actions(view $qbank): array {
return [
new column_action_move($qbank),
new column_action_remove($qbank),
new column_action_resize($qbank),
];
}
}
@@ -0,0 +1,89 @@
<?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_columnsortorder\external;
use context_system;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
use qbank_columnsortorder\column_manager;
/**
* External function for saving column sizes.
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class set_column_size extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'sizes' => new external_value(
PARAM_TEXT,
'Size for each column, as a JSON string representing [column => size]',
VALUE_DEFAULT,
null,
),
'global' => new external_value(
PARAM_BOOL,
'Set global config setting, rather than user preference',
VALUE_DEFAULT,
false
),
]);
}
/**
* Returns description of method result value.
*/
public static function execute_returns(): void {
}
/**
* Set sizes for columns
* Save against user preference if component is specified
*
* @param ?string $sizes json string representing [column => size]. Null value clears the setting.
* @param bool $global Set global config setting, rather than user preference
*/
public static function execute(?string $sizes, bool $global = false): void {
[
'sizes' => $sizes,
'global' => $global,
] = self::validate_parameters(
self::execute_parameters(),
[
'sizes' => $sizes,
'global' => $global,
]
);
$context = context_system::instance();
self::validate_context($context);
if ($global) {
require_capability('moodle/site:config', $context);
}
column_manager::set_column_size($sizes, $global);
}
}
@@ -41,8 +41,14 @@ class set_columnbank_order extends external_api {
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'columns' => new external_multiple_structure(
new external_value(PARAM_TEXT, 'Plugin name for the column', VALUE_REQUIRED)
)
new external_value(PARAM_TEXT, 'Plugin name for the column', VALUE_REQUIRED),
'List of column in the desired order',
VALUE_DEFAULT,
null,
NULL_ALLOWED,
),
'global' => new external_value(PARAM_BOOL, 'Set global config setting, rather than user preference',
VALUE_DEFAULT, false),
]);
}
@@ -54,16 +60,26 @@ class set_columnbank_order extends external_api {
}
/**
* Returns the columns plugin order.
* Set columns order.
*
* @param array $columns json string representing new column order.
* @param ?array $columns List of column names in the desired order. Null value clears the setting.
* @param bool $global Set global config setting, rather than user preference
*/
public static function execute(array $columns): void {
['columns' => $columns] = self::validate_parameters(self::execute_parameters(), ['columns' => $columns]);
public static function execute(?array $columns, bool $global = false): void {
[
'columns' => $columns,
'global' => $global,
] = self::validate_parameters(self::execute_parameters(), [
'columns' => $columns,
'global' => $global,
]);
$context = context_system::instance();
self::validate_context($context);
require_capability('moodle/category:manage', $context);
if ($global) {
require_capability('moodle/site:config', $context);
}
column_manager::set_column_order($columns);
column_manager::set_column_order($columns, $global);
}
}
@@ -0,0 +1,92 @@
<?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_columnsortorder\external;
use context_system;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_value;
use qbank_columnsortorder\column_manager;
/**
* External function for saving the list of hidden columns.
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class set_hidden_columns extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'columns' => new external_multiple_structure(
new external_value(PARAM_TEXT, 'Plugin name for the hidden column', VALUE_REQUIRED),
'List of hidden columns',
VALUE_DEFAULT,
null,
NULL_ALLOWED,
),
'global' => new external_value(
PARAM_BOOL,
'Set global config setting, rather than user preference',
VALUE_DEFAULT,
false
),
]);
}
/**
* Returns description of method result value.
*
*/
public static function execute_returns(): void {
}
/**
* Set hidden columns
* Save against user preference if specified
*
* @param ?array $columns List of hidden columns. Null value clears the setting.
* @param bool $global Set global config setting, rather than user preference
*/
public static function execute(?array $columns, bool $global = false): void {
[
'columns' => $columns,
'global' => $global,
] = self::validate_parameters(
self::execute_parameters(),
[
'columns' => $columns,
'global' => $global,
]
);
$context = context_system::instance();
self::validate_context($context);
if ($global) {
require_capability('moodle/site:config', $context);
}
column_manager::set_hidden_columns($columns, $global);
}
}
@@ -0,0 +1,53 @@
<?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_columnsortorder\local\bank;
use core_question\local\bank\column_action_base;
use core_question\local\bank\column_base;
/**
* Move a column
*
* This will add an action menu item which will be enhanced by javascript in user_actions.js to show the move column modal for the
* current column when clicked.
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_action_move extends column_action_base {
/** @var string Label for the Move action. */
protected string $move;
protected function init(): void {
$this->move = get_string('move');
}
public function get_action_menu_link(column_base $column): ?\action_menu_link {
return new \action_menu_link_secondary(
new \moodle_url('/question/edit.php'),
null,
$this->move,
[
'title' => get_string('movecolumn', 'qbank_columnsortorder', $column->get_title()),
'data-action' => 'move',
'data-column' => get_class($column),
]
);
}
}
@@ -0,0 +1,78 @@
<?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_columnsortorder\local\bank;
use core_question\local\bank\column_action_base;
use core_question\local\bank\column_base;
/**
* Remove a column
*
* This action will display a link that will set the current column as hidden, then redirect back the current page.
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_action_remove extends column_action_base {
/** @var bool True if we are changing global config, false for user preferences. */
protected bool $global;
/** @var string Label for the Remove action. */
protected string $remove;
protected function init(): void {
$this->global = false;
$this->remove = get_string('remove');
}
/**
* Set the $global property to indicate whether we are changing global config.
*
* This action is used on both the user and admin screens, so requires this additional method.
*
* @param bool $global
* @return void
*/
public function set_global(bool $global): void {
$this->global = $global;
}
public function get_action_menu_link(column_base $column): ?\action_menu_link {
$actionurl = new \moodle_url('/question/bank/columnsortorder/actions.php', [
'column' => $column->get_column_id(),
'action' => 'remove',
'sesskey' => sesskey(),
'returnurl' => new \moodle_url($this->qbank->returnurl),
]);
if ($this->global) {
$actionurl->param('global', $this->global);
}
return new \action_menu_link_secondary(
$actionurl,
null,
$this->remove,
[
'class' => 'action-link',
'title' => get_string('removecolumn', 'qbank_columnsortorder', $column->get_title()),
'data-action' => 'remove',
'data-column' => $column->get_column_id(),
]
);
}
}
@@ -0,0 +1,53 @@
<?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_columnsortorder\local\bank;
use core_question\local\bank\column_action_base;
use core_question\local\bank\column_base;
/**
* Resize a column
*
* This will add an action menu item which will be enhanced by javascript in user_actions.js to show the resize column modal for the
* current column when clicked.
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_action_resize extends column_action_base {
/** @var string Label for the resize action. */
protected string $resize;
protected function init(): void {
$this->resize = get_string('resize', 'qbank_columnsortorder');
}
public function get_action_menu_link(column_base $column): ?\action_menu_link {
return new \action_menu_link_secondary(
new \moodle_url('/question/edit.php'),
null,
$this->resize,
[
'title' => get_string('resizecolumn', 'qbank_columnsortorder', $column->get_title()),
'data-action' => 'resize',
'data-column' => get_class($column),
]
);
}
}
@@ -0,0 +1,99 @@
<?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_columnsortorder\local\bank;
use core_question\local\bank\view;
use qbank_columnsortorder\column_manager;
/**
* Custom view for displaying a preview of the question bank
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class preview_view extends view {
/**
* Use global settings for the column manager.
*
* @return void
*/
protected function init_column_manager(): void {
$this->columnmanager = new column_manager(true);
}
/**
* Prints the table row with the preview data for each column.
*
* @param \stdClass $question
* @param int $rowcount
*/
public function print_table_row($question, $rowcount): void {
$rowclasses = implode(' ', $this->get_row_classes($question, $rowcount));
$attributes = [];
if ($rowclasses) {
$attributes['class'] = $rowclasses;
}
echo \html_writer::start_tag('tr', $attributes);
foreach ($this->visiblecolumns as $column) {
$column->display_preview($question, $rowclasses);
}
echo \html_writer::end_tag('tr');
foreach ($this->extrarows as $row) {
$row->display_preview($question, $rowclasses);
}
}
/**
* Get a dummy question containing valid data for the default question fields.
*
* @return \stdClass
*/
protected function get_dummy_question(): \stdClass {
return (object)[
'id' => 1,
'qtype' => 'truefalse',
'createdby' => 2,
'categoryid' => 1,
'contextid' => 1,
'status' => 'ready',
'version' => 1,
'versionid' => 1,
'questionbankentryid' => 1,
'name' => 'Lorem ipsum',
'idnumber' => 123,
'creatorfirstname' => 'Admin',
'creatorlastname' => 'User',
'timecreated' => 1691157311,
'modifierfirstname' => 'Admin',
'modifierlastname' => 'User',
'timemodified' => 1691157311,
];
}
/**
* Generate a preview of the question bank table with a single dummy question.
*
* @return string An HTML table containing the column headings and a single question row.
*/
public function get_preview(): string {
ob_start();
$this->display_questions([$this->get_dummy_question()]);
return ob_get_clean();
}
}
@@ -0,0 +1,77 @@
<?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_columnsortorder\output;
use core_reportbuilder\local\models\column;
use qbank_columnsortorder\column_manager;
use moodle_url;
use renderer_base;
/**
* Renderable for the "add column" dropdown list
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class add_column implements \renderable, \templatable {
/** @var column_manager The column manager for getting the list of hidden columns. */
protected column_manager $columnmanager;
/** @var moodle_url The current page URL to redirect back to. */
protected moodle_url $returnurl;
/** @var bool True if we are changing global config, false for user preferences. */
protected bool $global;
/**
* Store arguments for generating template context.
*
* @param column_manager $columnmanager
* @param moodle_url $returnurl
* @param bool $global
*/
public function __construct(column_manager $columnmanager, moodle_url $returnurl, bool $global = false) {
$this->columnmanager = $columnmanager;
$this->returnurl = $returnurl;
$this->global = $global;
}
public function export_for_template(renderer_base $output): array {
$hiddencolumns = [];
foreach ($this->columnmanager->get_hidden_columns() as $class => $name) {
$addurl = new moodle_url('/question/bank/columnsortorder/actions.php', [
'action' => 'add',
'global' => $this->global,
'column' => $class,
'sesskey' => sesskey(),
'returnurl' => $this->returnurl,
]);
$hiddencolumns[] = [
'name' => $name,
'addurl' => $addurl->out(false),
'column' => $class,
'addtext' => get_string('addcolumn', 'qbank_columnsortorder', $name),
];
}
return [
'hashiddencolumns' => !empty($hiddencolumns),
'hiddencolumns' => $hiddencolumns,
];
}
}
@@ -0,0 +1,54 @@
<?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_columnsortorder\output;
use moodle_url;
use templatable;
use renderable;
use qbank_columnsortorder\column_manager;
/**
* Renderable for the question bank preview.
*
* This takes the HTML for a question bank preview, and displays in a page with a link to return to the admin screen.
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_sort_preview implements renderable, templatable {
/** @var string Rendered preview HTML. */
protected string $preview;
/**
* Store rendered preview for template context.
*
* @param string $preview
*/
public function __construct(string $preview) {
$this->preview = $preview;
}
public function export_for_template(\renderer_base $output): array {
$context = [
'backurl' => new moodle_url('/question/bank/columnsortorder/sortcolumns.php'),
'preview' => $this->preview,
];
return $context;
}
}
@@ -0,0 +1,94 @@
<?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_columnsortorder\output;
use core_question\local\bank\column_base;
use qbank_columnsortorder\local\bank\column_action_remove;
use moodle_url;
use qbank_columnsortorder\column_manager;
use renderable;
use templatable;
/**
* Renderable for the column sort admin UI.
*
* Displays a list of the currently enabled columns and allows them to be sorted, hidden, and resized.
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_sort_ui implements renderable, templatable {
/**
* @var int The minimum custom width for a column.
*/
const MIN_COLUMN_WIDTH = 10;
public function export_for_template(\renderer_base $output): array {
$columnmanager = new column_manager(true);
$enabledcolumns = $columnmanager->get_columns();
$disabledcolumns = $columnmanager->get_disabled_columns();
$columnsizes = $columnmanager->get_colsize_map();
$qbank = $columnmanager->get_questionbank();
$returnurl = new moodle_url('/question/bank/columnsortorder/sortcolumns.php');
$params = [];
$params['formaction'] = new moodle_url('/question/bank/columnsortorder/actions.php');
$params['sesskey'] = sesskey();
$params['disabled'] = $disabledcolumns;
$params['contextid'] = \context_system::instance()->id;
$params['minwidth'] = self::MIN_COLUMN_WIDTH;
foreach ($enabledcolumns as $column) {
if (in_array($column->id, $columnmanager->hiddencolumns) || array_key_exists($column->id, $disabledcolumns)) {
continue;
}
$name = $column->name;
$colname = get_string('qbankcolumnname', 'qbank_columnsortorder', $column->colname);
$removeaction = new column_action_remove($qbank);
$removeaction->set_global(true);
$actionmenu = new \action_menu([
$removeaction->get_action_menu_link($column->class::from_column_name($qbank, $column->colname)),
]);
$params['names'][] = [
'name' => $name,
'colname' => $colname,
'class' => $column->class,
'width' => $columnsizes[$column->id] ?? null,
'widthlabel' => get_string('width', 'qbank_columnsortorder', $name),
'actionmenu' => $actionmenu->export_for_template($output),
'columnid' => $column->id,
'escapedid' => str_replace('\\', '__', $column->id),
];
}
$params['disabled'] = array_values($disabledcolumns);
$params['columnsdisabled'] = !empty($params['disabled']);
$addcolumn = new add_column($columnmanager, $returnurl);
$params['addcolumn'] = $addcolumn->export_for_template($output);
$resetcolums = new reset_columns($returnurl);
$params['resetcolumns'] = $resetcolums->export_for_template($output);
$params['extraclasses'] = 'pr-1';
$urltoredirect = new moodle_url('/admin/settings.php', ['section' => 'manageqbanks']);
$params['urltomanageqbanks'] = get_string('qbankgotomanageqbanks', 'qbank_columnsortorder', $urltoredirect->out());
$params['previewurl'] = new moodle_url('/question/bank/columnsortorder/sortcolumns.php', [
'preview' => true,
]);
return $params;
}
}
@@ -0,0 +1,37 @@
<?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_columnsortorder\output;
/**
* Output fragments for the column sort order interface
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class fragment {
/**
* Render the column sort UI with the current global config.
*
* @return string
*/
public static function column_sort_ui(): string {
global $OUTPUT;
return $OUTPUT->render(new \qbank_columnsortorder\output\column_sort_ui());
}
}
@@ -1,57 +0,0 @@
<?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_columnsortorder\output;
use moodle_url;
use plugin_renderer_base;
use qbank_columnsortorder\column_manager;
/**
* Class renderer.
* @package qbank_columnsortorder
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Ghaly Marc-Alexandre <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Render list of question bank plugin columns.
*
* @return string The rendered HTML.
*/
public function render_column_sort_ui() {
$columnsortorder = new column_manager();
$enabledcolumns = $columnsortorder->get_columns();
$disabledcolumns = $columnsortorder->get_disabled_columns();
$params = [];
foreach ($enabledcolumns as $columnname) {
$name = $columnname->name;
$colname = get_string('qbankcolumnname', 'qbank_columnsortorder', $columnname->colname);
if ($columnname->class === 'qbank_customfields\custom_field_column') {
$columnname->class .= "\\$columnname->colname";
}
$params['names'][] = ['name' => $name, 'colname' => $colname, 'class' => $columnname->class];
}
$params['disabled'] = $disabledcolumns;
$params['columnsdisabled'] = (!empty($params['disabled'])) ? true : false;
$urltoredirect = new moodle_url('/admin/settings.php', ['section' => 'manageqbanks']);
$params['urltomanageqbanks'] = get_string('qbankgotomanageqbanks', 'qbank_columnsortorder', $urltoredirect->out());
return $this->render_from_template('qbank_columnsortorder/columnsortorder', $params);
}
}
@@ -0,0 +1,60 @@
<?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_columnsortorder\output;
use renderer_base;
/**
* Renderable for resetting customised column settings.
*
* This will display a link that resets all customised column settings and redirects back to the current page.
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reset_columns implements \renderable, \templatable {
/** @var \moodle_url The current page URL to redirect back to. */
protected \moodle_url $returnurl;
/** @var bool True if we are changing global config, false for user preferences. */
protected bool $global;
/**
* Store data for generating the template context.
*
* @param \moodle_url $returnurl
* @param bool $global
*/
public function __construct(\moodle_url $returnurl, bool $global = false) {
$this->returnurl = $returnurl;
$this->global = $global;
}
public function export_for_template(renderer_base $output): array {
$reseturl = new \moodle_url('/question/bank/columnsortorder/actions.php', [
'action' => 'reset',
'global' => $this->global,
'sesskey' => sesskey(),
'returnurl' => $this->returnurl->out(),
]);
return [
'reseturl' => $reseturl->out(false),
];
}
}
@@ -0,0 +1,64 @@
<?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_columnsortorder;
use core\context;
use core_question\local\bank\column_manager_base;
use core_question\local\bank\plugin_features_base;
use core_question\local\bank\view;
use qbank_columnsortorder\output\add_column;
use qbank_columnsortorder\output\reset_columns;
/**
* Plugin features for qbank_columnsortorder
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class plugin_feature extends plugin_features_base {
/**
* Override the default column manager.
*
* This will set the column order, size and visibility based on the global settings defined on the admin screen, or on the
* current user's preference if they have set one.
*
* @return ?column_manager_base
*/
public function get_column_manager(): ?column_manager_base {
return new column_manager();
}
/**
* Return add and reset column controls.
*
* @param view $qbank The question bank view.
* @param context $context The current context, for permission checks.
* @param int $categoryid The current question category ID.
* @return \renderable[]
*/
public function get_question_bank_controls(view $qbank, context $context, int $categoryid): array {
global $PAGE;
$PAGE->requires->js_call_amd('qbank_columnsortorder/user_actions', 'init');
$returnurl = new \moodle_url($qbank->returnurl);
return [
200 => new add_column(new column_manager(), $returnurl),
300 => new reset_columns($returnurl),
];
}
}
@@ -16,7 +16,8 @@
namespace qbank_columnsortorder\privacy;
use \core_privacy\local\metadata\null_provider;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\writer;
/**
* Privacy provider for columnsortorder.
@@ -26,14 +27,32 @@ use \core_privacy\local\metadata\null_provider;
* @author Ghaly Marc-Alexandre <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
class provider implements
// The forms subsystem does not store any data itself, it has no database tables.
\core_privacy\local\metadata\provider,
// The forms subsystem has user preferences.
\core_privacy\local\request\user_preference_provider {
public static function get_metadata(collection $collection): collection {
$collection->add_user_preference('enabledcol', 'privacy:metadata:preference:enabledcol');
$collection->add_user_preference('hiddencols', 'privacy:metadata:preference:hiddencols');
$collection->add_user_preference('colsize', 'privacy:metadata:preference:colsize');
return $collection;
}
public static function export_user_preferences(int $userid) {
$components = ['core_question', "qbank_history"];
foreach ($components as $component) {
$prefnames = ['enabledcol', 'hiddencols', 'colsize'];
foreach ($prefnames as $name) {
$preference = get_user_preferences("{$component}_{$name}", null, $userid);
if ($preference !== null) {
$desc = get_string('enabledcol', 'privacy:metadata:preference:enabledcol');
writer::export_user_preference('qbank_columnsortorder', "{$component}_{$name}", $preference, $desc);
}
}
}
}
}
@@ -0,0 +1,92 @@
<?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/>.
/**
* Class defining resuable tests methods for external functions
*
* @package qbank_columnsortorder
* @copyright 2023 Catalyst IT Europe Ltd.
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbank_columnsortorder\tests;
/**
* Class defining resuable tests methods for external functions
*/
abstract class external_function_testcase extends \advanced_testcase {
/**
* @var string Fully-qualified external function class to test.
*/
protected $testclass;
/**
* @var string The name of the setting used to store the data.
*/
protected $setting;
/**
* @var bool Whether the data is stored as a comma-separated list.
*/
protected $csv = true;
/**
* A function that returns the data to be passed to the external function.
*
* The data returned will depend on the testclass.
*
* @return mixed
*/
abstract protected function generate_test_data(): mixed;
/**
* Test that execute() method sets the correct config setting.
*/
public function test_execute(): void {
$this->resetAfterTest(true);
$this->setAdminUser();
$testdata = $this->generate_test_data();
$this->testclass::execute($testdata, true);
$currentconfig = get_config('qbank_columnsortorder', $this->setting);
if ($this->csv) {
$currentconfig = explode(',', $currentconfig);
}
$this->assertEqualsCanonicalizing($testdata, $currentconfig);
}
/**
* Test that execute() method sets user preference when a component is passed.
*/
public function test_execute_user(): void {
$this->resetAfterTest(true);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$testdata = $this->generate_test_data();
$this->testclass::execute($testdata);
$userpreference = get_user_preferences('qbank_columnsortorder_' . $this->setting);
if ($this->csv) {
$userpreference = explode(',', $userpreference);
}
$this->assertEqualsCanonicalizing($testdata, $userpreference);
}
}
+16 -4
View File
@@ -14,9 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* qbank_columnsortorder external functions and service definitions.
*
* @package qbank_columnsortorder
* @category webservice
* @copyright 2021 Catalyst IT Australia Pty Ltd
@@ -28,9 +28,21 @@ defined('MOODLE_INTERNAL') || die();
$functions = [
'qbank_columnsortorder_set_columnbank_order' => [
'classname' => 'qbank_columnsortorder\external\set_columnbank_order',
'classname' => 'qbank_columnsortorder\external\set_columnbank_order',
'description' => 'Sets question columns order in database',
'type' => 'write',
'ajax' => true,
'type' => 'write',
'ajax' => true,
],
'qbank_columnsortorder_set_hidden_columns' => [
'classname' => 'qbank_columnsortorder\external\set_hidden_columns',
'description' => 'Hidden Columns',
'type' => 'write',
'ajax' => true,
],
'qbank_columnsortorder_set_column_size' => [
'classname' => 'qbank_columnsortorder\external\set_column_size',
'description' => 'Column size',
'type' => 'write',
'ajax' => true,
],
];
@@ -23,11 +23,25 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['addcolumn'] = 'Add column \'{$a}\'';
$string['addcolumns'] = 'Add columns';
$string['auto'] = 'Auto';
$string['columnwidth'] = 'Column width (pixels)';
$string['invalidwidth'] = 'Width must be at least 10.';
$string['movecolumn'] = 'Move column \'{$a}\'';
$string['pluginname'] = 'Column sort order';
$string['privacy:metadata'] = 'The Column sort order question bank plugin does not store any personal data.';
$string['privacy:metadata:preference:enabledcol'] = 'The plugin saves user preference of column orders.';
$string['privacy:metadata:preference:hiddencols'] = 'The plugin saves user preference of hidden columns.';
$string['privacy:metadata:preference:colsize'] = 'The plugin saves user preference of column sizes.';
$string['qbankcolumnsortorder'] = 'Column sort order';
$string['qbankgotocolumnsort'] = 'You can change the order of the columns in the question bank on the page {$a}.';
$string['qbankcolumnsdisabled'] = 'Currently disabled question bank plugins:';
$string['qbankgotomanageqbanks'] = 'You can remove a column by disabling the plugin in <a href=\'{$a}\'>Manage question bank plugins</a>.';
$string['qbankcolumnname'] = '({$a})';
$string['qbankcolumnname'] = '{$a}';
$string['qbanksortdescription'] = 'The order in which plugins are listed below determines the order of the columns in the question bank.';
$string['resetcolumns'] = 'Reset columns';
$string['resize'] = 'Resize';
$string['resizecolumn'] = 'Resize column {$a}';
$string['removecolumn'] = 'Remove column \'{$a}\'';
$string['showhidecolumn'] = 'Show/Hide column';
$string['width'] = 'Width of \'{$a}\' in pixels';
+36
View File
@@ -0,0 +1,36 @@
<?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/>.
/**
* Standard callback functions for qbank_columnsortorder
*
* This file only exists for defining fragment callbacks. Do not include any other functions here.
*
* @package qbank_columnsortorder
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Render the column sort UI. {@see \qbank_columnsortorder\output\fragment::column_sort_ui()}
*
* @param array $args
* @return string
*/
function qbank_columnsortorder_output_fragment_column_sort_ui(array $args): string {
return \qbank_columnsortorder\output\fragment::column_sort_ui();
}
@@ -29,9 +29,16 @@ require_once($CFG->libdir.'/adminlib.php');
admin_externalpage_setup('qbank_columnsortorder', '', ['section' => 'columnsortorder'],
'/question/bank/columnsortorder/sortcolumns.php');
$renderer = $PAGE->get_renderer('qbank_columnsortorder');
$preview = optional_param('preview', false, PARAM_BOOL);
echo $OUTPUT->header();
echo $OUTPUT->heading(new lang_string('qbankcolumnsortorder', 'qbank_columnsortorder'));
echo $renderer->render_column_sort_ui();
if ($preview) {
$columnmanager = new \qbank_columnsortorder\column_manager(true);
$preview = $columnmanager->get_questionbank()->get_preview();
echo $OUTPUT->render(new \qbank_columnsortorder\output\column_sort_preview($preview));
} else {
echo $OUTPUT->render(new \qbank_columnsortorder\output\column_sort_ui());
}
echo $OUTPUT->footer();
+43
View File
@@ -0,0 +1,43 @@
#page-admin-question-bank-columnsortorder-sortcolumns .addcolumn {
display: inline-block;
}
.qbank-sortable-column {
background-color: white;
position: relative;
}
.jsenabled .qbank-column-list button.savewidths {
display: none;
}
.qbank-sortable-column .qbank_columnsortorder-action-handle {
display: none;
}
.qbank-sortable-column.show-handles .qbank_columnsortorder-action-handle {
display: block;
}
.qbank-sortable-column .handle-container {
pointer-events: none; /* Prevent the handle container blocking clicks to elements in the header */
width: 100%;
position: absolute;
top: 40%;
z-index: 1;
}
.qbank_columnsortorder-action-handle {
pointer-events: auto; /* Ensure the handles themselves receive clicks */
}
.qbank_columnsortorder-action-handle.move {
margin-left: -21px;
width: 16px;
}
.qbank_columnsortorder-action-handle.resize {
cursor: col-resize;
position: absolute;
top: 0;
right: 5px;
}
.qbank_columnsortorder-action-handle.resize img {
/* Prevent the resize handle icon being dragged across the page */
pointer-events: none;
}
.qbank_columnsortorder-action-handle .icon {
margin-right: 0;
}
@@ -0,0 +1,46 @@
{{!
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_columnsortorder/action_handle
Render a single action handle for customising a column.
Example context (json):
{
"action": "move",
"target": "",
"title": "Move column A",
"pixicon": "i/dragdrop",
"pixcomponent": "core",
"popup": true
}
}}
<span class="qbank_columnsortorder-action-handle {{action}}" tabindex="0" role="button"{{#popup}} aria-haspopup="true"{{/popup}}
data-action="{{action}}"
data-target="{{target}}"
{{#dragtype}}data-drag-type="{{dragtype}}" {{/dragtype}}
title="{{title}}">
{{#pixicon}}
{{#pix}}{{pixicon}}, {{pixcomponent}}{{/pix}}
{{/pixicon}}
{{#icon}}
<i class="fa fa-{{{icon}}} mr-1" aria-hidden="true"></i>
{{/icon}}
{{#text}}
{{text}}
{{/text}}
</span>
@@ -0,0 +1,57 @@
{{!
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_columnsortorder/add_column
Add column widget
Displays a list of currently hidden columns, with links to add them back to the question bank.
Context variables required for this template:
* hiddencolumns - list of currently hidden columns
Example context (json):
{
"hashiddencolumns": true,
"hiddencolumns": [
{
"name": "Column A",
"class": "class_name_A",
"addurl": "/question/bank/columnsortorder/actions.php?action=add&column=class_name_A"
},
{
"name": "Column B",
"class": "class_name_B",
"addurl": "/question/bank/columnsortorder/actions.php?action=add&column=class_name_B"
}
]
}
}}
{{#hashiddencolumns}}
<div class="dropdown addcolumn">
<button class="btn btn-outline-dark dropdown-toggle ml-1" data-toggle="dropdown" id="addcolumndropdown" aria-haspopup="true" aria-expanded="false">
{{#str}}addcolumns, qbank_columnsortorder{{/str}}
</button>
<div class="dropdown-menu" aria-labelledby="addcolumndropdown">
{{#hiddencolumns}}
<a class="dropdown-item action-link" href="{{addurl}}" title="{{addtext}}" data-action="add" data-column="{{column}}">
{{name}}
</a>
{{/hiddencolumns}}
</div>
</div>
{{/hashiddencolumns}}
@@ -0,0 +1,37 @@
{{!
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_columnsortorder/column_sort_preview
Display a preview of the question bank, with a button to return to the customisation UI.
Context variables required for this template:
* backurl - URL of customisation UI page.
* preview - Rendered HTML for a question bank table with the current settings.
Example context (json):
{
"backurl": "https://example.com/question/bank/columnsortorder/sortcolumns.php",
"preview": "<table></table>"
}
}}
<div id="qbank_columnsortorder-{{uniqid}}" class="container">
<a class="btn btn-primary" href="{{backurl}}">Back</a>
{{{preview}}}
</div>
@@ -0,0 +1,181 @@
{{!
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_columnsortorder/column_sort_ui
Display a list of enable columns with customisation controls, and a list of columns from disabled plugins, with a link to the
question bank plugin management screen.
Context variables required for this template:
* contextid - Current context id.
* formaction - The URL of the actions controller to submit the form to.
* sesskey - The current sesskey.
* previewurl - The URL of the question bank preview page.
* addcolumn - The context for the qbank_columnsortorder/add_column template.
* resetcolumns - The context for the qbank_columnsortorder/reset_columns template.
* names - A list of the currently active columns
* name - The display name of the column
* tiptitle - The tooltip text for the move handle.
* colname - The unique class name for the column from the plugin.
* escapedclass - The class name with \ replaced by __.
* widthlabel - The label text of for this column's width field.
* minwidth - The minimum value for the width field.
* width - The current value for the width field.
* actionmenu - The context for core/action_menu, a list of actions for the column.
* columnsdisabled - Are the any columns defined by disabled plugins?
* disabled - A list of disabled column names.
* disabledname - The disabled column's name.
* urltomanageqbanks - Link to the page for managing qbank plugins.
Example context (json):
{
"contextid": 1,
"formaction": "https://example.com/question/bank/columnsortorder/actions.php",
"sesskey": "12345abcde",
"previewurl": "https://example.com/question/bank/columnsortorder/sortcolumns.php?preview=1",
"addcolumn": {
"hashiddencolumns": true,
"hiddencolumns": [
{
"name": "Column A",
"class": "class_name_A",
"addurl": "/question/bank/columnsortorder/actions.php?action=add&column=class_name_A"
},
{
"name": "Column B",
"class": "class_name_B",
"addurl": "/question/bank/columnsortorder/actions.php?action=add&column=class_name_B"
}
]
},
"resetcolumns": {
"reseturl": "https://example.com/question/bank/columnsortorder/actions.php?action=reset"
},
"names": [
{
"name": "Column A",
"tiptitle": "Move Column A",
"columnid": "qbank_example\\col_name_A-col_name_A",
"escapedid": "qbank_example__col_name_A-col_name_A",
"widthlabel": "Width of Column A",
"minwidth": "10",
"width": ""
},
{
"name": "Column B",
"tiptitle": "Move Column B",
"columnid": "qbank_example\\col_name_B-col_name_B",
"escapedid": "qbank_example__col_name_B-col_name_B",
"minwidth": "10",
"width": "200"
}
],
"columnsdisabled": true,
"disabled": [
{
"disabledname": "disabled_1"
},
{
"disabledname": "disabled_2"
}
],
"urltomanageqbanks": "<a href=\"https://example.com/admin/manageqbankplugins.php\">Manage qbank plugins</a>"
}
}}
<div id="qbank_columnsortorder-{{uniqid}}" {{!
}}class="container" {{!
}}data-component="qbank_columnsortorder" {{!
}}data-callback="column_sort_ui" {{!
}}data-contextid="{{contextid}}">
<p>
{{#str}}qbanksortdescription, qbank_columnsortorder{{/str}}
</p>
<form class="has-validation" action="{{formaction}}" method="post">
<input type="hidden" name="sesskey" value="{{sesskey}}">{{!
}}<div class="d-grid gap-2 d-md-flex justify-content-between mb-2">
<div>
<a class="btn btn-primary" href="{{previewurl}}">
{{#str}}preview{{/str}}
</a>
{{#addcolumn}}
{{>qbank_columnsortorder/add_column}}
{{/addcolumn}}
</div>
{{#resetcolumns}}
{{>qbank_columnsortorder/reset_columns}}
{{/resetcolumns}}
</div>
<table class="generaltable table table-fixed">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">{{#str}}name{{/str}}</th>
<th scope="col">{{#str}}plugin{{/str}}</th>
<th scope="col" class="w-25">{{#str}}columnwidth, qbank_columnsortorder{{/str}}</th>
<th scope="col">{{#str}}action{{/str}}</th>
</tr>
</thead>
<tbody class="qbank-column-list">
{{#names}}
<tr class="qbank-sortable-column" data-pluginname="{{class}}" data-name="{{name}}" data-columnid="{{columnid}}">
<td>{{>core/drag_handle}}</td>
<td>{{name}}</td>
<td>{{colname}}</td>
<td>
<div class="input-group">
<label class="sr-only" for="width_{{escapedid}}">{{widthlabel}}</label>
<input id="width_{{escapedid}}" {{!
}}class="form-control width-input" {{!
}}type="number" {{!
}}min="{{minwidth}}" {{!
}}name="width[{{escapedid}}]" {{!
}}placeholder="{{#str}}auto, qbank_columnsortorder{{/str}}" {{!
}}value="{{width}}">
<button class="btn btn-sm btn-outline-dark savewidths" type="submit" name="action" value="savewidths">
{{#str}}save{{/str}}
</button>
<div class="invalid-feedback">
{{#str}}invalidwidth, qbank_columnsortorder{{/str}}
</div>
</div>
</td>
<td>{{#actionmenu}}{{>core/action_menu}}{{/actionmenu}}</td>
</tr>
{{/names}}
</tbody>
</table>
</form>
<div>
{{#columnsdisabled}}
{{#str}}qbankcolumnsdisabled, qbank_columnsortorder{{/str}}
<br>
{{/columnsdisabled}}
{{#disabled}}
<div class="list-group-item disabled">
{{disabledname}}
</div>
{{/disabled}}
</div>
<div>
{{{urltomanageqbanks}}}
</div>
{{#js}}
require(['qbank_columnsortorder/admin_actions'], function(AdminTable) {
AdminTable.init("qbank_columnsortorder-{{uniqid}}");
});
{{/js}}
</div>
@@ -1,65 +0,0 @@
{{!
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_columnsortorder/columnsortorder.mustache
Admin question bank setting template.
Context variables required for this template:
* name - plugin name
* hiddenname - class/columname
Example context (json):
{
"name": "Creatorname (creator_name_column)",
"hiddenname": "creator_name_column"
}
}}
<div>
{{#str}}qbanksortdescription, qbank_columnsortorder{{/str}}
</div>
<br>
<div class="list list-group" id="qbank_columnsortorder-{{uniqid}}">
{{#names}}
<div class="column item list-group-item list-group-item-action" data-pluginname={{class}}>
{{>core/drag_handle}}
{{name}} {{colname}}
</div>
{{/names}}
</div>
<br>
<div>
{{#columnsdisabled}}
{{#str}}qbankcolumnsdisabled, qbank_columnsortorder{{/str}}
<br>
{{/columnsdisabled}}
{{#disabled}}
<div class="list-group-item disabled">
{{disabledname}}
</div>
{{/disabled}}
</div>
{{#js}}
require(['qbank_columnsortorder/sort_columns'], function(SortColumns) {
SortColumns.init("qbank_columnsortorder-{{uniqid}}");
});
{{/js}}
<br>
<div>
{{{urltomanageqbanks}}}
</div>
@@ -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_columnsortorder/handle_container
Container for move/resize handles.
Example context (json):
{
}
}}
<div class="handle-container">
</div>
@@ -0,0 +1,33 @@
{{!
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_columnsortorder/reset_columns
Button to reset question bank column settings to defaults.
Context variables required for this template:
Example context (json):
{
"reseturl": "https://example.com/question/bank/columnsortorder/actions.php?action=reset"
}
}}
<span>
<a href="{{reseturl}}" class="btn btn-outline-dark action-link ml-1" data-action="reset">
{{#str}}resetcolumns, qbank_columnsortorder{{/str}}
</a>
</span>
@@ -0,0 +1,29 @@
{{!
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_columnsortorder/resize_modal
This template renders the content of the resize modal, a number input to set the column width in pixels.
Example context (json):
{
}
}}
<label>
{{#str}}columnwidth, qbank_columnsortorder{{/str}}
<input name="columnwidth" type="number" value="">
</label>
@@ -0,0 +1,118 @@
@qbank @qbank_columnsortorder @javascript
Feature: Set default question bank column order and size
In order to set sensible defaults for the question bank interface
As an admin
I want to hide, reorder, and resize columns
Scenario: Admin can reorder question bank columns
Given I change the window size to "large"
And I log in as "admin"
When I navigate to "Plugins > Question bank plugins > Column sort order" in site administration
And I drag "Created by" "qbank_columnsortorder > column move handle" and I drop it in "T" "qbank_columnsortorder > column move handle"
Then "Created by" "table_row" should appear before "T" "table_row"
And I reload the page
And "Created by" "table_row" should appear before "T" "table_row"
And I follow "Preview"
And "Created by" "qbank_columnsortorder > column header" should appear before "T" "qbank_columnsortorder > column header"
Scenario: Disabling a question bank plugin removes its columns
Given I log in as "admin"
When I navigate to "Plugins > Question bank plugins > Column sort order" in site administration
And I should see "Created by"
And I click on "Manage question bank plugins" "link"
And I click on "Disable" "link" in the "View creator" "table_row"
And I click on "Column sort order" "link"
Then "Currently disabled question bank plugins:" "text" should appear before "Created by" "text"
And I click on "Manage question bank plugins" "link"
And I click on "Enable" "link" in the "View creator" "table_row"
And I click on "Column sort order" "link"
Then I should not see "Currently disabled question bank plugins:"
And I should see "Created by"
Scenario: Admin can hide a column in site administration page
Given I log in as "admin"
And I navigate to "Plugins > Question bank plugins > Column sort order" in site administration
And "Created by" "table_row" should exist
When I click on "Actions menu" "link" in the "Created by" "table_row"
And I choose "Remove" in the open action menu
Then "Created by" "table_row" should not exist
And I reload the page
And "Created by" "table_row" should not exist
And I follow "Preview"
And "Created by" "qbank_columnsortorder > column header" should not exist
Scenario: Admin can show a column in site administration page
Given the following config values are set as admin:
| config | value | plugin |
| hiddencols | qbank_viewcreator\creator_name_column-creator_name_column | qbank_columnsortorder |
And I log in as "admin"
And I navigate to "Plugins > Question bank plugins > Column sort order" in site administration
And "Created by" "table_row" should not exist
When I press "Add columns"
And I follow "Created by"
Then "Created by" "table_row" should exist
And I reload the page
And "Created by" "table_row" should exist
And I follow "Preview"
And "Created by" "qbank_columnsortorder > column header" should exist
Scenario: Admin can resize a column in site administration page
Given I log in as "admin"
And I navigate to "Plugins > Question bank plugins > Column sort order" in site administration
And the field "Width of 'Question' in pixels" matches value ""
When I set the field "Width of 'Question' in pixels" to "400"
And I reload the page
Then the field "Width of 'Question' in pixels" matches value "400"
And I follow "Preview"
And the "style" attribute of "Question" "qbank_columnsortorder > column header" should contain "width: 400px"
Scenario: Custom fields can be reordered, resized, hidden and shown
Given I change the window size to "large"
And I log in as "admin"
And I navigate to "Plugins > Question bank plugins > Question custom fields" in site administration
And I press "Add a new category"
And I click on "Add a new custom field" "link"
And I follow "Checkbox"
And I set the following fields to these values:
| Name | checkboxcustomcolumn |
| Short name | chckcust |
And I press "Save changes"
And I should see "checkboxcustomcolumn"
And I navigate to "Plugins > Question bank plugins > Column sort order" in site administration
And "checkboxcustomcolumn" "table_row" should appear after "Comments" "table_row"
When I drag "checkboxcustomcolumn" "qbank_columnsortorder > column move handle" and I drop it in "Comments" "qbank_columnsortorder > column move handle"
And I set the field "Width of 'checkboxcustomcolumn' in pixels" to "200"
And I follow "Preview"
And "checkboxcustomcolumn" "qbank_columnsortorder > column header" should appear before "Comments" "qbank_columnsortorder > column header"
And the "style" attribute of "checkboxcustomcolumn" "qbank_columnsortorder > column header" should contain "width: 200px"
And I follow "Back"
And "checkboxcustomcolumn" "table_row" should appear before "Comments" "table_row"
And the field "Width of 'checkboxcustomcolumn' in pixels" matches value "200"
And I click on "Actions menu" "link" in the "checkboxcustomcolumn" "table_row"
And I choose "Remove" in the open action menu
And "checkboxcustomcolumn" "table_row" should not exist
And I follow "Preview"
And "checkboxcustomcolumn" "qbank_columnsortorder > column header" should not exist
And I follow "Back"
And I press "Add columns"
And I follow "checkboxcustomcolumn"
And "checkboxcustomcolumn" "table_row" should exist
And I follow "Preview"
And "checkboxcustomcolumn" "qbank_columnsortorder > column header" should exist
Scenario: Resetting columns on the admin page clears global settings
Given the following config values are set as admin:
| config | value | plugin |
| hiddencols | qbank_usage\question_last_used_column-question_last_used_column | qbank_columnsortorder |
| enabledcol | qbank_comment\comment_count_column-comment_count_column,qbank_viewquestionname\question_name_idnumber_tags_column-question_name_idnumber_tags_column,core_question\local\bank\edit_menu_column-edit_menu_column,qbank_editquestion\question_status_column-question_status_column,qbank_history\version_number_column-version_number_column,qbank_statistics\columns\discrimination_index:discrimination_index,qbank_statistics\columns\facility_index:facility_index,qbank_statistics\columns\discriminative_efficiency:discriminative_efficiency,qbank_usage\question_usage_column-question_usage_column,qbank_usage\question_last_used_column-question_last_used_column,qbank_viewcreator\creator_name_column-creator_name_column,qbank_viewcreator\modifier_name_column-modifier_name_column,qbank_viewquestiontype\question_type_column-question_type_column | qbank_columnsortorder |
| colsize | [{"column":"qbank_comment\\comment_count_column-comment_count_column","width":"20"},{"column":"qbank_viewquestionname\\question_name_idnumber_tags_column-question_name_idnumber_tags_column","width":"300"},{"column":"qbank_editquestion\\question_status_column-question_status_column","width":"20"},{"column":"qbank_history\\version_number_column-version_number_column","width":"20"},{"column":"qbank_statistics\\columns\\discrimination_index:discrimination_index","width":"20"},{"column":"qbank_statistics\\columns\\facility_index:facility_index","width":"20"},{"column":"qbank_statistics\\columns\\discriminative_efficiency:discriminative_efficiency","width":"20"},{"column":"qbank_usage\\question_usage_column-question_usage_column","width":"20"},{"column":"qbank_viewcreator\\creator_name_column-creator_name_column","width":"200"},{"column":"qbank_viewcreator\\modifier_name_column-modifier_name_column","width":"200"},{"column":"qbank_viewquestiontype\\question_type_column-question_type_column","width":"20"},{"column":"core_question\\local\\bank\\edit_menu_column-edit_menu_column","width":"50"}] | qbank_columnsortorder |
And I change the window size to "large"
And I log in as "admin"
And I navigate to "Plugins > Question bank plugins > Column sort order" in site administration
And "Last used" "table_row" should not exist
And "Comments" "table_row" should appear before "Question" "table_row"
And the field "Width of 'Question' in pixels" matches value "300"
When I follow "Reset columns"
Then "Last used" "table_row" should exist
And "Question" "table_row" should appear before "Comments" "table_row"
And the field "Width of 'Question' in pixels" matches value ""
@@ -0,0 +1,43 @@
<?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/>.
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
/**
* Steps definitions related with the drag and drop header.
* @package qbank_columnsortorder
* @category test
* @copyright 2022 Catalyst IT Australia Pty Ltd
* @author Nathan Nguyen <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_qbank_columnsortorder extends behat_base {
public static function get_exact_named_selectors(): array {
return [
new behat_component_named_selector('column header', [
"//th[contains(@data-name, %locator%)]",
]),
new behat_component_named_selector('column move handle', [
"//*[self::th or self::tr][contains(@data-name, %locator%)]//span[contains(@data-drag-type, 'move')]",
]),
new behat_component_named_selector('column resize handle', [
"//th[contains(@data-name, %locator%)]//span[contains(@data-action, 'resize')]",
]),
];
}
}
@@ -1,92 +0,0 @@
@qbank @qbank_columnsortorder @javascript
Feature: An plugin column can be reordered and displayed in the question bank view.
In order to reorganise the question bank view columns
As an admin
I need to be able to reorder them
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 "activity" exists:
| activity | quiz |
| course | C1 |
| name | Test quiz Q001 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "question category" exist:
| contextlevel | reference | name |
| Activity module | Test quiz Q001 | Question category 1 |
And the following "questions" exist:
| questioncategory | qtype | name | user | questiontext | idnumber |
| Question category 1 | essay | Test question to be seen | teacher1 | Write about whatever you want | idnumber1 |
Scenario: Teacher can see proper view
Given I am on the "Test quiz Q001" "mod_quiz > question bank" page logged in as "teacher1"
When I apply question bank filter "Category" with value "Question category 1"
And I should see "Test question to be seen"
Then I should see "Teacher 1"
Scenario: Reordering question bank columns
Given I log in as "admin"
When I navigate to "Plugins > Question bank plugins > Column sort order" in site administration
And I drag "Created by (creator_name_column)" "text" and I drop it in "T (question_type_column)" "text"
And I am on the "Test quiz Q001" "mod_quiz > question bank" page logged in as "teacher1"
And I apply question bank filter "Category" with value "Question category 1"
Then ".creatorname" "css_element" should appear before ".qtype" "css_element"
Scenario: Disabling and enabling column display is proper
Given I log in as "admin"
When I navigate to "Plugins > Question bank plugins > Column sort order" in site administration
And I should see "Created by (creator_name_column)"
And I click on "Manage question bank plugins" "link"
And I click on "Disable" "link" in the "View creator" "table_row"
And I click on "Column sort order" "link"
Then "Currently disabled question bank plugins:" "text" should appear before "Created by" "text"
And I should not see "Created by (creator_name_column)"
And I click on "Manage question bank plugins" "link"
And I click on "Enable" "link" in the "View creator" "table_row"
And I click on "Column sort order" "link"
Then I should not see "Currently disabled question bank plugins:"
And I should see "Created by (creator_name_column)"
Scenario: Custom fields are reorderable
Given I log in as "admin"
When I navigate to "Plugins > Question bank plugins > Question custom fields" in site administration
And I press "Add a new category"
And I click on "Add a new custom field" "link"
And I follow "Checkbox"
And I set the following fields to these values:
| Name | checkboxcustomcolumn |
| Short name | chckcust |
And I press "Save changes"
Then I should see "checkboxcustomcolumn"
And I navigate to "Plugins > Question bank plugins > Column sort order" in site administration
And I should see "checkboxcustomcolumn"
And I change the window size to "large"
And I drag "checkboxcustomcolumn" "text" and I drop it in "T (question_type_column)" "text"
Then "checkboxcustomcolumn" "text" should appear before "T (question_type_column)" "text"
And I click on "Manage question bank plugins" "link"
And I click on "Disable" "link" in the "Question custom fields" "table_row"
And I click on "Column sort order" "link"
Then "Currently disabled question bank plugins:" "text" should appear before "chckcust" "text"
And I click on "Manage question bank plugins" "link"
And I click on "Enable" "link" in the "Question custom fields" "table_row"
And I click on "Column sort order" "link"
Then I should not see "Currently disabled question bank plugins:"
And I should see "checkboxcustomcolumn"
Scenario: Reordering with disabled columns
When I log in as "admin"
And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
And I click on "Disable" "link" in the "Question statistics" "table_row"
And I click on "Enable" "link" in the "Question statistics" "table_row"
And I click on "Disable" "link" in the "Question statistics" "table_row"
And I am on the "Course 1" "core_question > course question bank" page
Then I should see "Question bank"
And "Create a new question" "button" should exist
# Really, we are just checking the question bank displayed without errors.
@@ -0,0 +1,143 @@
@qbank @qbank_columnsortorder @javascript
Feature: Set question bank column order and size
In order customise my view of the question bank
As a teacher
I want to hide, reorder, and resize columns
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 "activity" exists:
| activity | quiz |
| course | C1 |
| name | Test quiz Q001 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "question category" exist:
| contextlevel | reference | name |
| Activity module | Test quiz Q001 | Question category 1 |
And the following "questions" exist:
| questioncategory | qtype | name | user | questiontext | idnumber |
| Question category 1 | essay | Test question to be seen | teacher1 | Write about whatever you want | idnumber1 |
And the following config values are set as admin:
| config | value | plugin |
| hiddencols | qbank_usage\question_last_used_column-question_last_used_column | qbank_columnsortorder |
| enabledcol | qbank_comment\comment_count_column-comment_count_column,qbank_viewquestionname\question_name_idnumber_tags_column-question_name_idnumber_tags_column,core_question\local\bank\edit_menu_column-edit_menu_column,qbank_editquestion\question_status_column-question_status_column,qbank_history\version_number_column-version_number_column,qbank_statistics\columns\discrimination_index:discrimination_index,qbank_statistics\columns\facility_index:facility_index,qbank_statistics\columns\discriminative_efficiency:discriminative_efficiency,qbank_usage\question_usage_column-question_usage_column,qbank_usage\question_last_used_column-question_last_used_column,qbank_viewcreator\creator_name_column-creator_name_column,qbank_viewcreator\modifier_name_column-modifier_name_column,qbank_viewquestiontype\question_type_column-question_type_column | qbank_columnsortorder |
| colsize | [{"column":"qbank_comment\\comment_count_column-comment_count_column","width":"20"},{"column":"qbank_viewquestionname\\question_name_idnumber_tags_column-question_name_idnumber_tags_column","width":"300"},{"column":"qbank_editquestion\\question_status_column-question_status_column","width":"20"},{"column":"qbank_history\\version_number_column-version_number_column","width":"20"},{"column":"qbank_statistics\\columns\\discrimination_index:discrimination_index","width":"20"},{"column":"qbank_statistics\\columns\\facility_index:facility_index","width":"20"},{"column":"qbank_statistics\\columns\\discriminative_efficiency:discriminative_efficiency","width":"20"},{"column":"qbank_usage\\question_usage_column-question_usage_column","width":"20"},{"column":"qbank_viewcreator\\creator_name_column-creator_name_column","width":"200"},{"column":"qbank_viewcreator\\modifier_name_column-modifier_name_column","width":"200"},{"column":"qbank_viewquestiontype\\question_type_column-question_type_column","width":"20"},{"column":"core_question\\local\\bank\\edit_menu_column-edit_menu_column","width":"50"}] | qbank_columnsortorder |
Scenario: The teacher sees the question bank with global settings initially
Given I am on the "Test quiz Q001" "mod_quiz > question bank" page logged in as "teacher1"
When I apply question bank filter "Category" with value "Question category 1"
Then I should see "Test question to be seen"
And "Last used" "qbank_columnsortorder > column header" should not exist
And "Comments" "qbank_columnsortorder > column header" should appear before "Question" "qbank_columnsortorder > column header"
And the "style" attribute of "Question" "qbank_columnsortorder > column header" should contain "width: 300px;"
Scenario: User preference takes precedence over global defaults
Given the following "user preferences" exist:
| user | preference | value |
| teacher1 | qbank_columnsortorder_hiddencols | qbank_comment\comment_count_column-comment_count_column |
| teacher1 | qbank_columnsortorder_enabledcol | qbank_viewquestionname\question_name_idnumber_tags_column-question_name_idnumber_tags_column,qbank_usage\question_last_used_column-question_last_used_column,core_question\local\bank\edit_menu_column-edit_menu_column,qbank_editquestion\question_status_column-question_status_column,qbank_history\version_number_column-version_number_column,qbank_statistics\columns\discrimination_index:discrimination_index,qbank_statistics\columns\facility_index:facility_index,qbank_statistics\columns\discriminative_efficiency:discriminative_efficiency,qbank_usage\question_usage_column-question_usage_column,qbank_usage\question_last_used_column-question_last_used_column,qbank_viewcreator\creator_name_column-creator_name_column,qbank_viewcreator\modifier_name_column-modifier_name_column,qbank_viewquestiontype\question_type_column-question_type_column |
| teacher1 | qbank_columnsortorder_colsize | [{"column":"qbank_comment\\comment_count_column-comment_count_column","width":"20"},{"column":"qbank_viewquestionname\\question_name_idnumber_tags_column-question_name_idnumber_tags_column","width":"400"},{"column":"qbank_editquestion\\question_status_column-question_status_column","width":"20"},{"column":"qbank_history\\version_number_column-version_number_column","width":"20"},{"column":"qbank_statistics\\columns\\discrimination_index:discrimination_index","width":"20"},{"column":"qbank_statistics\\columns\\facility_index:facility_index","width":"20"},{"column":"qbank_statistics\\columns\\discriminative_efficiency:discriminative_efficiency","width":"20"},{"column":"qbank_usage\\question_usage_column-question_usage_column","width":"20"},{"column":"qbank_viewcreator\\creator_name_column-creator_name_column","width":"200"},{"column":"qbank_viewcreator\\modifier_name_column-modifier_name_column","width":"200"},{"column":"qbank_viewquestiontype\\question_type_column-question_type_column","width":"20"},{"column":"core_question\\local\\bank\\edit_menu_column-edit_menu_column","width":"50"}] |
And I am on the "Test quiz Q001" "mod_quiz > question bank" page logged in as "teacher1"
When I apply question bank filter "Category" with value "Question category 1"
Then "Comments" "qbank_columnsortorder > column header" should not exist
And "Question" "qbank_columnsortorder > column header" should appear before "Last used" "qbank_columnsortorder > column header"
And the "style" attribute of "Question" "qbank_columnsortorder > column header" should contain "width: 400px;"
Scenario: Resetting user view goes back to global defaults
Given the following "user preferences" exist:
| user | preference | value |
| teacher1 | qbank_columnsortorder_hiddencols | |
| teacher1 | qbank_columnsortorder_enabledcol | qbank_viewquestionname\question_name_idnumber_tags_column-question_name_idnumber_tags_column,qbank_usage\question_last_used_column-question_last_used_column,core_question\local\bank\edit_menu_column-edit_menu_column,qbank_editquestion\question_status_column-question_status_column,qbank_history\version_number_column-version_number_column,qbank_statistics\columns\discrimination_index:discrimination_index,qbank_statistics\columns\facility_index:facility_index,qbank_statistics\columns\discriminative_efficiency:discriminative_efficiency,qbank_usage\question_usage_column-question_usage_column,qbank_usage\question_last_used_column-question_last_used_column,qbank_viewcreator\creator_name_column-creator_name_column,qbank_viewcreator\modifier_name_column-modifier_name_column,qbank_viewquestiontype\question_type_column-question_type_column |
| teacher1 | qbank_columnsortorder_colsize | [{"column":"qbank_comment\\comment_count_column-comment_count_column","width":"20"},{"column":"qbank_viewquestionname\\question_name_idnumber_tags_column-question_name_idnumber_tags_column","width":"400"},{"column":"qbank_editquestion\\question_status_column-question_status_column","width":"20"},{"column":"qbank_history\\version_number_column-version_number_column","width":"20"},{"column":"qbank_statistics\\columns\\discrimination_index:discrimination_index","width":"20"},{"column":"qbank_statistics\\columns\\facility_index:facility_index","width":"20"},{"column":"qbank_statistics\\columns\\discriminative_efficiency:discriminative_efficiency","width":"20"},{"column":"qbank_usage\\question_usage_column-question_usage_column","width":"20"},{"column":"qbank_viewcreator\\creator_name_column-creator_name_column","width":"200"},{"column":"qbank_viewcreator\\modifier_name_column-modifier_name_column","width":"200"},{"column":"qbank_viewquestiontype\\question_type_column-question_type_column","width":"20"},{"column":"core_question\\local\\bank\\edit_menu_column-edit_menu_column","width":"50"}] |
And I am on the "Test quiz Q001" "mod_quiz > question bank" page logged in as "teacher1"
And I apply question bank filter "Category" with value "Question category 1"
And "Last used" "qbank_columnsortorder > column header" should exist
And "Question" "qbank_columnsortorder > column header" should appear before "Last used" "qbank_columnsortorder > column header"
And the "style" attribute of "Question" "qbank_columnsortorder > column header" should contain "width: 400px;"
When I follow "Reset columns"
Then "Last used" "qbank_columnsortorder > column header" should not exist
And "Comments" "qbank_columnsortorder > column header" should appear before "Question" "qbank_columnsortorder > column header"
And the "style" attribute of "Question" "qbank_columnsortorder > column header" should contain "width: 300px;"
Scenario: User can remove a column in the question bank
Given I am on the "Test quiz Q001" "mod_quiz > question bank" page logged in as "teacher1"
And I apply question bank filter "Category" with value "Question category 1"
And "Comments" "qbank_columnsortorder > column header" should exist
And I click on "Actions menu" "link" in the "Comments" "qbank_columnsortorder > column header"
And I choose "Remove" in the open action menu
Then "Comments" "qbank_columnsortorder > column header" should not exist
And I reload the page
And "Comments" "qbank_columnsortorder > column header" should not exist
Scenario: User can add a column in the question bank
Given I am on the "Test quiz Q001" "mod_quiz > question bank" page logged in as "teacher1"
And I apply question bank filter "Category" with value "Question category 1"
And "Last used" "qbank_columnsortorder > column header" should not exist
When I press "Add columns"
And I follow "Last used"
Then "Last used" "qbank_columnsortorder > column header" should exist
And I reload the page
And "Last used" "qbank_columnsortorder > column header" should exist
Scenario: User can resize a column in the question bank using modal dialog
Given I am on the "Test quiz Q001" "mod_quiz > question bank" page logged in as "teacher1"
And I apply question bank filter "Category" with value "Question category 1"
And the "style" attribute of "Question" "qbank_columnsortorder > column header" should contain "width: 300px"
When I click on "Actions menu" "link" in the "Question" "qbank_columnsortorder > column header"
And I choose "Resize" in the open action menu
And I set the field "Column width (pixels)" to "400"
And I press "Save changes"
Then the "style" attribute of "Question" "qbank_columnsortorder > column header" should contain "width: 400px"
And I reload the page
And the "style" attribute of "Question" "qbank_columnsortorder > column header" should contain "width: 400px"
Scenario: User can resize a column in the question bank by dragging
Given I change the window size to "large"
And I am on the "Test quiz Q001" "mod_quiz > question bank" page logged in as "teacher1"
And I apply question bank filter "Category" with value "Question category 1"
And the "style" attribute of "Question" "qbank_columnsortorder > column header" should contain "width: 300px"
When I hover "Question" "qbank_columnsortorder > column header"
And I drag "Question" "qbank_columnsortorder > column resize handle" and I drop it in "Status" "qbank_columnsortorder > column header"
And the "style" attribute of "Question" "qbank_columnsortorder > column header" should not contain "width: 300px"
And I reload the page
And the "style" attribute of "Question" "qbank_columnsortorder > column header" should not contain "width: 300px"
Scenario: User can move a column in the question bank using modal dialog
Given I am on the "Test quiz Q001" "mod_quiz > question bank" page logged in as "teacher1"
And I apply question bank filter "Category" with value "Question category 1"
And "Comments" "qbank_columnsortorder > column header" should appear before "Question" "qbank_columnsortorder > column header"
When I click on "Actions menu" "link" in the "Comments" "qbank_columnsortorder > column header"
And I choose "Move" in the open action menu
And I follow "After \"Question\""
Then "Comments" "qbank_columnsortorder > column header" should appear after "Question" "qbank_columnsortorder > column header"
And I reload the page
And "Comments" "qbank_columnsortorder > column header" should appear after "Question" "qbank_columnsortorder > column header"
Scenario: User can move a column in the question bank by dragging
Given I change the window size to "large"
And I am on the "Test quiz Q001" "mod_quiz > question bank" page logged in as "teacher1"
And I apply question bank filter "Category" with value "Question category 1"
And "Comments" "qbank_columnsortorder > column header" should appear before "Question" "qbank_columnsortorder > column header"
When I hover "Comments" "qbank_columnsortorder > column header"
And I drag "Comments" "qbank_columnsortorder > column move handle" and I drop it in "Status" "qbank_columnsortorder > column header"
Then "Comments" "qbank_columnsortorder > column header" should appear after "Question" "qbank_columnsortorder > column header"
And I reload the page
And "Comments" "qbank_columnsortorder > column header" should appear after "Question" "qbank_columnsortorder > column header"
Scenario: Reordering with disabled columns
When I log in as "admin"
And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
And I click on "Disable" "link" in the "Question statistics" "table_row"
And I click on "Enable" "link" in the "Question statistics" "table_row"
And I click on "Disable" "link" in the "Question statistics" "table_row"
And I am on the "Course 1" "core_question > course question bank" page
Then I should see "Question bank"
And "Create a new question" "button" should exist
# Really, we are just checking the question bank displayed without errors.
@@ -20,10 +20,10 @@ defined('MOODLE_INTERNAL') || die();
use advanced_testcase;
use context_course;
use core_question\local\bank\column_base;
use core_question\local\bank\question_edit_contexts;
use core_question\local\bank\view;
use moodle_url;
use qbank_columnsortorder\external\set_columnbank_order;
global $CFG;
require_once($CFG->dirroot . '/question/tests/fixtures/testable_core_question_column.php');
@@ -36,43 +36,147 @@ require_once($CFG->dirroot . '/question/classes/external.php');
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Ghaly Marc-Alexandre <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \qbank_columnsortorder\column_manager
* @covers \qbank_columnsortorder\column_manager
*/
class column_manager_test extends advanced_testcase {
/** @var \stdClass course record. */
protected $course;
/** @var \core_question\local\bank\view */
protected $questionbank;
/** @var array */
protected $columns;
/** @var \qbank_columnsortorder\column_manager */
protected $columnmanager;
/**
* Generate a course and return a question bank view for the course context.
*
* @return view
*/
protected static function get_question_bank(): view {
$course = self::getDataGenerator()->create_course();
$questionbank = new view(
new question_edit_contexts(context_course::instance($course->id)),
new moodle_url('/'),
$course
);
return $questionbank;
}
/**
* Setup testcase.
* Return an array of visible columns for the question bank.
*
* @param view $questionbank
* @return array
*/
public function setUp(): void {
$this->resetAfterTest(true);
$this->setAdminUser();
$this->course = $this->getDataGenerator()->create_course();
// Creates question bank view.
$this->questionbank = new view(
new question_edit_contexts(context_course::instance($this->course->id)),
new moodle_url('/'),
$this->course
);
// Get current view columns.
$this->columns = [];
foreach ($this->questionbank->get_visiblecolumns() as $columnn) {
$this->columns[] = get_class($columnn);
protected static function get_columns(view $questionbank): array {
$columns = [];
foreach ($questionbank->get_visiblecolumns() as $column) {
$columns[] = $column->get_column_id();
}
$this->columnmanager = new column_manager();
return $columns;
}
/**
* Provide examples for testing each column setting function, with test data and data format.
*
* @return array[]
*/
public static function settings_provider(): array {
$questionbank = self::get_question_bank();
return [
'Test set_column_order' => [
'setting' => 'enabledcol',
'function' => 'set_column_order',
'data' => self::get_columns($questionbank),
'csv' => true,
],
'Test set_hidden_columns' => [
'setting' => 'hiddencols',
'function' => 'set_hidden_columns',
'data' => self::get_columns($questionbank),
'csv' => true,
],
'Test set_column_size' => [
'setting' => 'colsize',
'function' => 'set_column_size',
'data' => random_string(),
'csv' => false,
],
];
}
/**
* Test setting config settings
*
* @dataProvider settings_provider
* @param string $setting The name of the setting being saved
* @param string $function The name of the function being called
* @param mixed $data The property of the test class to pass to the function.
* @param bool $csv True of the data is stored as a comma-separated list.
* @return void
*/
public function test_settings(string $setting, string $function, mixed $data, bool $csv): void {
$this->setAdminUser();
$this->resetAfterTest(true);
$this->assertFalse(get_config('qbank_columnsortorder', $setting));
$this->assertEmpty(get_user_preferences('qbank_columnsortorder_' . $setting));
column_manager::{$function}($data, true);
$expected = $csv ? implode(',', $data) : $data;
$this->assertEquals($expected, get_config('qbank_columnsortorder', $setting));
$this->assertEmpty(get_user_preferences('qbank_columnsortorder_' . $setting));
}
/**
* Test passing null clears the corresponding config setting.
*
* @dataProvider settings_provider
* @param string $setting The name of the setting being saved
* @param string $function The name of the function being called
* @param mixed $data The property of the test class to pass to the function.
* @param bool $csv True of the data is stored as a comma-separated list.
* @return void
*/
public function test_reset_settings(string $setting, string $function, mixed $data, bool $csv): void {
$this->setAdminUser();
$this->resetAfterTest(true);
$initial = $csv ? implode(',', $data) : $data;
set_config($setting, $initial, 'qbank_columnsortorder');
$this->assertEquals($initial, get_config('qbank_columnsortorder', $setting));
column_manager::{$function}(null, true);
$this->assertFalse(get_config('qbank_columnsortorder', $setting));
}
/**
* Test setting user preferences
*
* @dataProvider settings_provider
* @param string $setting The name of the setting being saved
* @param string $function The name of the function being called
* @param mixed $data The data to pass to the function.
* @param bool $csv True of the data is stored as a comma-separated list.
* @return void
*/
public function test_settings_user(string $setting, string $function, mixed $data, bool $csv): void {
$this->resetAfterTest(true);
$this->assertFalse(get_config('qbank_columnsortorder', $setting));
$this->assertEmpty(get_user_preferences('qbank_columnsortorder_' . $setting));
column_manager::{$function}($data);
$expected = $csv ? implode(',', $data) : $data;
$this->assertFalse(get_config('qbank_columnsortorder', $setting));
$this->assertEquals($expected, get_user_preferences('qbank_columnsortorder_' . $setting));
}
/**
* Test passing null clears the corresponding user preference.
*
* @dataProvider settings_provider
* @param string $setting The name of the setting being saved
* @param string $function The name of the function being called
* @param mixed $data The property of the test class to pass to the function.
* @param bool $csv True of the data is stored as a comma-separated list.
* @return void
*/
public function test_reset_user_settings(string $setting, string $function, mixed $data, bool $csv): void {
$this->setAdminUser();
$this->resetAfterTest(true);
$initial = $csv ? implode(',', $data) : $data;
set_user_preference('qbank_columnsortorder_' . $setting, $initial);
$this->assertEquals($initial, get_user_preferences('qbank_columnsortorder_' . $setting));
column_manager::{$function}(null);
$this->assertEmpty(get_user_preferences('qbank_columnsortorder_' . $setting));
}
/**
@@ -81,7 +185,10 @@ class column_manager_test extends advanced_testcase {
* @covers ::get_columns
*/
public function test_getcolumns_function(): void {
$questionlistcolumns = $this->columnmanager->get_columns();
$this->resetAfterTest(true);
$this->setAdminUser();
$columnmanager = new column_manager(true);
$questionlistcolumns = $columnmanager->get_columns();
$this->assertIsArray($questionlistcolumns);
foreach ($questionlistcolumns as $columnnobject) {
$this->assertObjectHasAttribute('class', $columnnobject);
@@ -91,45 +198,67 @@ class column_manager_test extends advanced_testcase {
}
/**
* Test function sort columns method.
* The get_sorted_columns method should return the provided columns sorted according to enabledcol setting.
*
* @covers ::get_sorted_columns
* @return void
*/
public function test_get_sorted_columns(): void {
$neworder = $this->columnmanager->get_sorted_columns($this->columns);
$this->resetAfterTest(true);
$this->setAdminUser();
$questionbank = $this->get_question_bank();
$columns = $this->get_columns($questionbank);
$neworder = $columns;
shuffle($neworder);
set_columnbank_order::execute($neworder);
$currentconfig = get_config('qbank_columnsortorder', 'enabledcol');
$currentconfig = explode(',', $currentconfig);
ksort($currentconfig);
$this->assertSame($neworder, $currentconfig);
set_config('enabledcol', implode(',', $neworder), 'qbank_columnsortorder');
$columnmanager = new column_manager(true);
$columnstosort = [];
foreach ($columns as $column) {
$columnstosort[$column] = $column;
}
$sortedcolumns = $columnmanager->get_sorted_columns($columnstosort);
$expectedorder = ['core_question\local\bank\checkbox_column' . column_base::ID_SEPARATOR . 'checkbox_column' => 0];
foreach ($neworder as $columnid) {
$expectedorder[$columnid] = $columnid;
}
$this->assertSame($expectedorder, $sortedcolumns);
}
/**
* Test function enabing and disablingcolumns.
* Test disabled columns are removed from enabledcol setting and added to disabledcol setting.
*
* @covers ::enable_columns
* @covers ::disable_columns
* @return void
*/
public function test_enable_disable_columns(): void {
$neworder = $this->columnmanager->get_sorted_columns($this->columns);
shuffle($neworder);
set_columnbank_order::execute($neworder);
$currentconfig = get_config('qbank_columnsortorder', 'enabledcol');
$currentconfig = explode(',', $currentconfig);
$class = $currentconfig[array_rand($currentconfig, 1)];
$randomplugintodisable = explode('\\', $class)[0];
$olddisabledconfig = get_config('qbank_columnsortorder', 'disabledcol');
$this->columnmanager->disable_columns($randomplugintodisable);
$newdisabledconfig = get_config('qbank_columnsortorder', 'disabledcol');
$this->assertNotEquals($olddisabledconfig, $newdisabledconfig);
$this->columnmanager->enable_columns($randomplugintodisable);
$newdisabledconfig = get_config('qbank_columnsortorder', 'disabledcol');
$this->assertEmpty($newdisabledconfig);
$enabledconfig = get_config('qbank_columnsortorder', 'enabledcol');
$contains = strpos($enabledconfig, $randomplugintodisable);
$this->assertNotFalse($contains);
$this->assertIsInt($contains);
public function test_disable_columns(): void {
$this->resetAfterTest(true);
$this->setAdminUser();
$questionbank = $this->get_question_bank();
$columns = $this->get_columns($questionbank);
// Set up enabledcol with all plugins.
set_config('enabledcol', implode(',', $columns), 'qbank_columnsortorder');
$questionbank = $this->get_question_bank();
$columns = $this->get_columns($questionbank);
$columnmanager = new column_manager(true);
$this->assertFalse(get_config('qbank_columnsortorder', 'disabledcol'));
// Disable a random plugin.
$plugincolumns = array_filter($columns, fn($column) => str_starts_with($column, 'qbank_'));
$randomcolumn = $plugincolumns[array_rand($plugincolumns, 1)];
$randomplugin = explode('\\', $randomcolumn)[0];
$columnmanager->disable_columns($randomplugin);
// The enabledcol setting should now contain all columns except the disabled plugin.
$expectedconfig = array_filter($columns, fn($column) => !str_starts_with($column, $randomplugin));
sort($expectedconfig);
$newconfig = explode(',', get_config('qbank_columnsortorder', 'enabledcol'));
sort($newconfig);
$this->assertEquals($expectedconfig, $newconfig);
$this->assertNotContains($randomcolumn, $newconfig);
// The disabledcol setting should only contain columns from the disabled plugin.
$disabledconfig = explode(',', get_config('qbank_columnsortorder', 'disabledcol'));
array_walk($disabledconfig, fn($column) => $this->assertStringStartsWith($randomplugin, $column));
}
/**
@@ -138,15 +267,21 @@ class column_manager_test extends advanced_testcase {
* @covers \qbank_columnsortorder\event\plugin_observer
*/
public function test_plugin_enabled_disabled_observers(): void {
$neworder = $this->columnmanager->get_sorted_columns($this->columns);
$this->resetAfterTest(true);
$this->setAdminUser();
$questionbank = $this->get_question_bank();
$columns = $this->get_columns($questionbank);
$columnmanager = new column_manager(true);
$neworder = $columnmanager->get_sorted_columns($columns);
shuffle($neworder);
set_columnbank_order::execute($neworder);
$columnmanager::set_column_order($neworder, true);
// Get the list of enabled columns, excluding core columns (we can't disable those).
$currentconfig = get_config('qbank_columnsortorder', 'enabledcol');
$currentconfig = array_filter(explode(',', $currentconfig), fn($class) => !str_starts_with($class, 'core'));
// Pick a column at random and get its plugin name.
$class = $currentconfig[array_rand($currentconfig, 1)];
$randomplugintodisable = explode('\\', $class)[0];
$randomcolumnid = $currentconfig[array_rand($currentconfig, 1)];
[$randomcolumnclass] = explode(column_base::ID_SEPARATOR, $randomcolumnid, 2);
[$randomplugintodisable] = explode('\\', $randomcolumnclass);
$olddisabledconfig = get_config('qbank_columnsortorder', 'disabledcol');
\core\event\qbank_plugin_disabled::create_for_plugin($randomplugintodisable)->trigger();
$newdisabledconfig = get_config('qbank_columnsortorder', 'disabledcol');
@@ -159,4 +294,79 @@ class column_manager_test extends advanced_testcase {
$this->assertNotFalse($contains);
$this->assertIsInt($contains);
}
/**
* Test enabled columns are removed from disabledcol setting and added to enabledcol setting.
*
* @return void
*/
public function test_enable_columns() {
$this->resetAfterTest(true);
$this->setAdminUser();
$questionbank = $this->get_question_bank();
$columns = $this->get_columns($questionbank);
// Set up disablecol with columns from 2 random plugins, and enabledcol with all other columns.
$plugincolumns = array_filter($columns, fn($column) => str_starts_with($column, 'qbank_'));
$plugins = array_unique(array_map(fn($column) => explode('\\', $column)[0], $plugincolumns));
$randomplugins = array_rand($plugins, 2);
$randomplugin1 = $plugins[$randomplugins[0]];
$randomplugin2 = $plugins[$randomplugins[1]];
$disabledcols = array_filter(
$columns,
fn($column) => str_starts_with($column, $randomplugin1) || str_starts_with($column, $randomplugin2)
);
$enabledcols = array_diff($columns, $disabledcols);
set_config('enabledcol', implode(',', $enabledcols), 'qbank_columnsortorder');
set_config('disabledcol', implode(',', $disabledcols), 'qbank_columnsortorder');
// Enable one of the disabled plugins.
$columnmanager = new column_manager(true);
$columnmanager->enable_columns($randomplugin1);
// The enabledcol setting should now contain all columns except the remaining disabled plugin.
$expectedenabled = array_filter($columns, fn($column) => !str_starts_with($column, $randomplugin2));
$expecteddisabled = array_filter($disabledcols, fn($column) => str_starts_with($column, $randomplugin2));
sort($expectedenabled);
sort($expecteddisabled);
$newenabled = explode(',', get_config('qbank_columnsortorder', 'enabledcol'));
sort($newenabled);
$this->assertEquals($expectedenabled, $newenabled);
$this->assertNotContains(reset($expecteddisabled), $newenabled);
// The disabledcol setting should only contain columns from the remaining disabled plugin.
$newdisabled = explode(',', get_config('qbank_columnsortorder', 'disabledcol'));
array_walk($newdisabled, fn($column) => $this->assertStringStartsWith($randomplugin2, $column));
}
/**
* Test that get_disabled_columns returns names of all the columns in the disabledcol setting
*
* @return void
*/
public function test_get_disabled_columns(): void {
$this->resetAfterTest(true);
$this->setAdminUser();
$questionbank = $this->get_question_bank();
$columns = $this->get_columns($questionbank);
// Set up disablecol with columns from 2 random plugins, and enabledcol with all other columns.
$plugincolumns = array_filter($columns, fn($column) => str_starts_with($column, 'qbank_'));
$randomcolumn = $plugincolumns[array_rand($plugincolumns, 1)];
$randomplugin = explode('\\', $randomcolumn)[0];
$disabledcols = array_filter($columns, fn($column) => str_starts_with($column, $randomplugin));
set_config('disabledcol', implode(',', $disabledcols), 'qbank_columnsortorder');
$columnmanager = new column_manager(true);
$expecteddisablednames = [];
foreach ($disabledcols as $disabledcolid) {
[$columnclass, $columnname] = explode(column_base::ID_SEPARATOR, $disabledcolid, 2);
$columnobject = $columnclass::from_column_name($questionbank, $columnname);
$expecteddisablednames[$disabledcolid] = (object) [
'disabledname' => $columnobject->get_title(),
];
}
$disablednames = $columnmanager->get_disabled_columns();
$this->assertEquals($expecteddisablednames, $disablednames);
}
}
@@ -0,0 +1,67 @@
<?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_columnsortorder\external;
use qbank_columnsortorder\column_manager;
use qbank_columnsortorder\tests\external_function_testcase;
// phpcs:disable moodle.PHPUnit.TestCaseNames.Missing
// This class inherits its test methods from the parent class.
/**
* Unit tests for qbank_columnsortorder external API.
*
* @package qbank_columnsortorder
* @copyright 2023 Catalyst IT Europe Ltd.
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \qbank_columnsortorder\external\set_column_size
*/
class set_column_size_test extends external_function_testcase {
/**
* @var string Fully-qualified external function class to test.
*/
protected $testclass = '\qbank_columnsortorder\external\set_column_size';
/**
* @var string The name of the setting used to store the data.
*/
protected $setting = 'colsize';
/**
* @var bool Whether the data is stored as a comma-separated list.
*/
protected $csv = false;
/**
* Generate a list of random column sizes.
*
* @return array
*/
protected function generate_test_data(): string {
$columnsortorder = new column_manager();
$questionlistcolumns = $columnsortorder->get_columns();
$columnsizes = [];
foreach ($questionlistcolumns as $columnnobject) {
$columnsizes[] = (object)[
'column' => $columnnobject->name,
'width' => rand(1, 100) . 'px',
];
}
return json_encode($columnsizes);
}
}
@@ -14,35 +14,41 @@
// 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_columnsortorder;
namespace qbank_columnsortorder\external;
use advanced_testcase;
use qbank_columnsortorder\column_manager;
use qbank_columnsortorder\external\set_columnbank_order;
use qbank_columnsortorder\tests\external_function_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/question/classes/external.php');
// phpcs:disable moodle.PHPUnit.TestCaseNames.Missing
// This class inherits its test methods from the parent class.
/**
* Unit tests for qbank_columnsortorder external API.
*
* @package qbank_columnsortorder
* @author 2021, Ghaly Marc-Alexandre <marc-alexandreghaly@catalyst-ca.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package qbank_columnsortorder
* @copyright 2023 Catalyst IT Europe Ltd.
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \qbank_columnsortorder\external\set_columnbank_order
*/
class columnsortorder_external_test extends advanced_testcase {
class set_columnbank_order_test extends external_function_testcase {
/**
* Test that external call core_question_external::set_columnbank_order($oldorder) sets proper
* data in config_plugins table.
* @var string Fully-qualified external function class to test.
*/
public function test_columnorder_external(): void {
$this->resetAfterTest(true);
$this->setAdminUser();
protected $testclass = '\qbank_columnsortorder\external\set_columnbank_order';
/**
* @var string The name of the setting used to store the data.
*/
protected $setting = 'enabledcol';
/**
* Return a randomly-ordered list of columns.
*
* @return array
*/
protected function generate_test_data(): array {
$columnsortorder = new column_manager();
$questionlistcolumns = $columnsortorder->get_columns();
$columnclasses = [];
@@ -50,11 +56,6 @@ class columnsortorder_external_test extends advanced_testcase {
$columnclasses[] = $columnnobject->class;
}
shuffle($columnclasses);
set_columnbank_order::execute($columnclasses);
$currentconfig = (array)get_config('qbank_columnsortorder', 'enabledcol');
$currentconfig = explode(',', $currentconfig[0]);
$this->assertSame($columnclasses, $currentconfig);
return $columnclasses;
}
}
@@ -0,0 +1,56 @@
<?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_columnsortorder\external;
use qbank_columnsortorder\column_manager;
use qbank_columnsortorder\tests\external_function_testcase;
// phpcs:disable moodle.PHPUnit.TestCaseNames.Missing
// This class inherits its test methods from the parent class.
/**
* Unit tests for qbank_columnsortorder external API.
*
* @package qbank_columnsortorder
* @copyright 2023 Catalyst IT Europe Ltd.
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \qbank_columnsortorder\external\set_hidden_columns
*/
class set_hidden_columns_test extends external_function_testcase {
/**
* @var string Fully-qualified external function class to test.
*/
protected $testclass = '\qbank_columnsortorder\external\set_hidden_columns';
/**
* @var string The name of the setting used to store the data.
*/
protected $setting = 'hiddencols';
/**
* Return a random list of hidden column names.
*
* @return array
*/
protected function generate_test_data(): array {
$columnsortorder = new column_manager();
$questionlistcolumns = $columnsortorder->get_columns();
$hiddencolumns = array_slice($questionlistcolumns, rand(0, count($questionlistcolumns) - 1));
return array_map(fn($column) => $column->name, $hiddencolumns);
}
}
+1 -1
View File
@@ -26,6 +26,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'qbank_columnsortorder';
$plugin->version = 2023042401;
$plugin->version = 2023052201;
$plugin->requires = 2023041800;
$plugin->maturity = MATURITY_STABLE;
@@ -110,4 +110,7 @@ class comment_count_column extends column_base {
return ['pr-3'];
}
public function get_default_width(): int {
return 150;
}
}
@@ -17,6 +17,8 @@
namespace qbank_customfields;
use core_question\local\bank\column_base;
use core_question\local\bank\view;
use qbank_customfields\customfield\question_handler;
/**
* A column type for the name of the question creator.
@@ -42,6 +44,16 @@ class custom_field_column extends column_base {
$this->field = $field;
}
public static function from_column_name(view $view, string $columnname): custom_field_column {
$handler = question_handler::create();
foreach ($handler->get_fields() as $field) {
if ($field->get('shortname') == $columnname) {
return new static($view, $field);
}
}
throw new \coding_exception('Custom field ' . $columnname . ' does not exist.');
}
/**
* Get the internal name for this column. Used as a CSS class name,
* and to store information about the current sort. Must match PARAM_ALPHA.
@@ -60,7 +72,7 @@ class custom_field_column extends column_base {
* @return string The unique name;
*/
public function get_column_name(): string {
return 'custom_field_column\\' . $this->field->get('shortname');
return $this->field->get('shortname');
}
/**
@@ -40,20 +40,21 @@ Feature: A teacher can edit question with custom fields
And I am on the "Test quiz name" "mod_quiz > question bank" page
Scenario: Edit a previously created question and see the custom field in the overview table and in the question preview.
When I choose "Edit question" action for "First question" in the question bank
And I should see "Category for test"
And I click on "Expand all" "link" in the "region-main" "region"
When I am on the "First question" "core_question > edit" page
Then I should see "Category for test"
And I expand all fieldsets
And I should see "Field 1"
And I set the following fields to these values:
| Field 1 | custom field text |
| Question name | First question edited |
| Field 1 | custom field text |
And I press "id_submitbutton"
And I should see "First question"
And I should see "custom field text"
And I choose "Preview" action for "First question" in the question bank
And I am on the "First question edited" "core_question > preview" page
And I should see "Field 1"
Then I should see "custom field text"
And I should see "custom field text"
Scenario: Preview a previously created question with custom fields set with empty values
When I choose "Preview" action for "First question" in the question bank
And I should see "Field 1"
Then I should not see "custom field text"
When I am on the "First question" "core_question > preview" page
Then I should see "Field 1"
And I should not see "custom field text"
@@ -37,13 +37,12 @@ Feature: The visibility of question custom fields control where they are display
| Correct answer | False |
| Feedback for the response 'True'. | So you think it is true |
| Feedback for the response 'False'. | So you think it is false |
And I am on the "Test quiz name" "mod_quiz > question bank" page
@javascript
Scenario: Display custom question fields to teachers based on their visibility.
When I choose "Edit question" action for "First question" in the question bank
And I should see "Category for test"
And I click on "Expand all" "link" in the "region-main" "region"
When I am on the "First question" "core_question > edit" page
Then I should see "Category for test"
And I expand all fieldsets
And I should see "Field 1"
And I should see "Field 2"
And I should see "Field 3"
@@ -63,4 +62,4 @@ Feature: The visibility of question custom fields control where they are display
And I should see "Field 2"
And I should see "custom field text two"
And I should see "Field 3"
Then I should see "secret"
And I should see "secret"
@@ -28,6 +28,7 @@
namespace qbank_editquestion;
use core_question\local\bank\question_version_status;
use qbank_editquestion\output\add_new_question;
/**
* Class editquestion_helper for methods related to add/edit/copy
@@ -69,25 +70,13 @@ class editquestion_helper {
* @param bool $canadd the text to display on the button.
* @param string $tooltip a tooltip to add to the button (optional).
* @param bool $disabled if true, the button will be disabled.
* @deprecated since Moodle 4.3. Use {@see add_new_question} renderable instead
* @todo Final deprecation in Moodle 4.7
*/
public static function create_new_question_button($categoryid, $params, $canadd, $tooltip = '', $disabled = false) {
global $PAGE, $OUTPUT;
$addquestiondisplay = array();
$addquestiondisplay['canadd'] = $canadd;
if ($canadd) {
$params['category'] = $categoryid;
$url = new \moodle_url('/question/bank/editquestion/addquestion.php', $params);
$buttonparams = ['disabled' => $disabled];
if (!empty($tooltip)) {
$buttonparams['title'] = $tooltip;
}
$addquestiondisplay['buttonhtml'] = $OUTPUT->single_button($url,
get_string('createnewquestion', 'question'),
'get', $buttonparams);
$addquestiondisplay['qtypeform'] = self::print_choose_qtype_to_add_form(array());
}
return $PAGE->get_renderer('qbank_editquestion')->render_create_new_question_button($addquestiondisplay);
global $OUTPUT;
debugging('create_new_question_button() is deprecated. Use the add_new_question renderable instead.');
return $OUTPUT->render(new add_new_question($categoryid, $params, $canadd));
}
/**
@@ -0,0 +1,68 @@
<?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_editquestion\output;
use qbank_editquestion\editquestion_helper;
use renderer_base;
/**
* Create new question button
*
* @package qbank_editquestion
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class add_new_question implements \renderable, \templatable {
/** @var int $categoryid The ID of the category the quesiton will be added to. */
protected int $categoryid;
/** @var array $params URL parameters to pass to the add question form. */
protected array $params;
/** @var bool $canadd True if the add question button should be displayed. If false, a placeholder will be shown.*/
protected bool $canadd;
/**
* Store data for building the template context.
*
* @param int $categoryid
* @param array $params
* @param bool $canadd
*/
public function __construct(int $categoryid, array $params, bool $canadd) {
$this->categoryid = $categoryid;
$this->params = $params;
$this->canadd = $canadd;
}
public function export_for_template(renderer_base $output): array {
$addquestiondisplay = [];
$addquestiondisplay['canadd'] = $this->canadd;
if ($this->canadd) {
$this->params['category'] = $this->categoryid;
$url = new \moodle_url('/question/bank/editquestion/addquestion.php', $this->params);
$addquestiondisplay['buttonhtml'] = $output->single_button(
$url,
get_string('createnewquestion', 'question'),
'get'
);
$addquestiondisplay['qtypeform'] = editquestion_helper::print_choose_qtype_to_add_form([]);
}
return $addquestiondisplay;
}
}
@@ -52,8 +52,12 @@ class renderer extends \plugin_renderer_base {
*
* @param array $addquestiondata
* @return bool|string
* @deprecated since Moodle 4.3. Use {@see add_new_question} renderable instead
* @todo Final deprecation in Moodle 4.7
*/
public function render_create_new_question_button ($addquestiondata) {
debugging('render_create_new_question_button() is deprecated. '
. 'Pass the add_new_question renderable to render() instead.');
return $this->render_from_template('qbank_editquestion/add_new_question', $addquestiondata);
}
@@ -25,7 +25,9 @@
namespace qbank_editquestion;
use core\context;
use core_question\local\bank\view;
use qbank_editquestion\output\add_new_question;
/**
* Class columns is the entrypoint for the columns.
@@ -35,7 +37,7 @@ use core_question\local\bank\view;
* @author Safat Shahin <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class plugin_feature extends \core_question\local\bank\plugin_features_base{
class plugin_feature extends \core_question\local\bank\plugin_features_base {
public function get_question_columns($qbank): array {
return [
@@ -50,4 +52,23 @@ class plugin_feature extends \core_question\local\bank\plugin_features_base{
];
}
/**
* Return "Add new question" control.
*
* @param view $qbank The question bank view.
* @param context $context The current context, for permission checks.
* @param int $categoryid The current question category ID.
* @return \renderable[]
*/
public function get_question_bank_controls(view $qbank, context $context, int $categoryid): array {
if (!$qbank->allow_add_questions()) {
return [];
}
$canadd = has_capability('moodle/question:add', $context);
$urlparams = (new edit_action($qbank))->editquestionurl->params();
return [
100 => new add_new_question($categoryid, $urlparams, $canadd),
];
}
}
@@ -92,8 +92,9 @@ class question_history_view extends view {
}
}
protected function create_new_question_form($category, $canadd): void {
public function allow_add_questions(): bool {
// As we dont want to create questions in this page.
return false;
}
/**
@@ -53,6 +53,14 @@ class discrimination_index extends column_base {
echo $PAGE->get_renderer('qbank_statistics')->render_discrimination_index($discriminationindex);
}
public function display_preview(\stdClass $question, string $rowclasses): void {
global $PAGE;
$this->display_start($question, $rowclasses);
echo $PAGE->get_renderer('qbank_statistics')->render_discrimination_index(50.00);
$this->display_end($question, $rowclasses);;
}
public function get_extra_classes(): array {
return ['pr-3'];
}
@@ -51,6 +51,14 @@ class discriminative_efficiency extends column_base {
echo $PAGE->get_renderer('qbank_statistics')->render_discriminative_efficiency($discriminativeefficiency);
}
public function display_preview(\stdClass $question, string $rowclasses): void {
global $PAGE;
$this->display_start($question, $rowclasses);
echo $PAGE->get_renderer('qbank_statistics')->render_discriminative_efficiency(25);
$this->display_end($question, $rowclasses);;
}
public function get_extra_classes(): array {
return ['pr-3'];
}
@@ -52,6 +52,14 @@ class facility_index extends column_base {
echo $PAGE->get_renderer('qbank_statistics')->render_facility_index($facility);
}
public function display_preview(\stdClass $question, string $rowclasses): void {
global $PAGE;
$this->display_start($question, $rowclasses);
echo $PAGE->get_renderer('qbank_statistics')->render_facility_index(0.75);
$this->display_end($question, $rowclasses);;
}
public function get_extra_classes(): array {
return ['pr-3'];
}
@@ -210,10 +210,8 @@ Feature: Show statistics in question bank
And I run the scheduled task "\quiz_statistics\task\recalculate"
# Confirm the "Needs checking?" column matches the expected values based on students' answers
When I am on the "Quiz 3" "mod_quiz > question bank" page logged in as "admin"
Then the following should exist in the "categoryquestions" table:
| Question | Needs checking? |
| MCA | Likely |
| MCB | Very likely |
| MCC | Unlikely |
| MCD | Likely |
| MCE | Very likely |
Then "Likely" "text" should exist in the "MCA" "table_row"
And "Very likely" "text" should exist in the "MCB" "table_row"
And "Unlikely" "text" should exist in the "MCC" "table_row"
And "Likely" "text" should exist in the "MCD" "table_row"
And "Very likely" "text" should exist in the "MCE" "table_row"
+1 -1
View File
@@ -1,4 +1,4 @@
#categoryquestions td.questionlastused span.date {
.question-bank-table td.questionlastused span.date {
font-weight: 400;
font-size: .8em;
}
@@ -50,8 +50,8 @@ class question_name_idnumber_tags_column extends viewquestionname_column_helper
// The non-breaking space '&nbsp;' is used in html to fix MDL-75051 (browser issues caused by chrome and Edge).
if ($question->idnumber !== null && $question->idnumber !== '') {
echo ' ' . \html_writer::span(
\html_writer::span(get_string('idnumber', 'question'), 'accesshide')
. '&nbsp;' . \html_writer::span(s($question->idnumber), 'badge badge-primary'), 'ml-1');
\html_writer::span(get_string('idnumber', 'question') . '&nbsp;', 'accesshide')
. \html_writer::span(s($question->idnumber), 'badge badge-primary'), 'ml-1');
}
// Question tags.
@@ -72,4 +72,7 @@ class viewquestionname_column_helper extends column_base {
return 'q.name';
}
public function get_default_width(): int {
return 250;
}
}
@@ -1,11 +0,0 @@
define("qbank_viewquestiontext/datafilter/filtertypes/showtext",["exports","core/datafilter/filtertype"],(function(_exports,_filtertype){var obj;
/**
* Filter managing question text display.
*
* @module qbank_viewquestiontext/datafilter/filtertypes/showtext
* @author 2022 Ghaly Marc-Alexandre <marc-alexandreghaly@catalyst-ca.net>
* @copyright 2022 Catalyst IT Australia Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_filtertype=(obj=_filtertype)&&obj.__esModule?obj:{default:obj};class _default extends _filtertype.default{}return _exports.default=_default,_exports.default}));
//# sourceMappingURL=showtext.min.js.map
@@ -1 +0,0 @@
{"version":3,"file":"showtext.min.js","sources":["../../../src/datafilter/filtertypes/showtext.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 * Filter managing question text display.\n *\n * @module qbank_viewquestiontext/datafilter/filtertypes/showtext\n * @author 2022 Ghaly Marc-Alexandre <[email protected]>\n * @copyright 2022 Catalyst IT Australia Pty Ltd\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport GenericFilter from 'core/datafilter/filtertype';\n\nexport default class extends GenericFilter {\n}\n"],"names":["GenericFilter"],"mappings":";;;;;;;;4KA0B6BA"}
@@ -0,0 +1,11 @@
define("qbank_viewquestiontext/question_text_format",["exports","qbank_viewquestiontext/repository","core_question/refresh_ui","core/notification"],(function(_exports,repository,_refresh_ui,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,repository=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}
/**
* Javascript for question_text_format question bank control.
*
* @module qbank_viewquestiontext/question_text_format
* @copyright 2023 Catalyst IT Europe Ltd.
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/(repository),_refresh_ui=_interopRequireDefault(_refresh_ui),_notification=_interopRequireDefault(_notification);const SELECTORS_formatSelectId="question-text-format",SELECTORS_returnUrl="[name=returnurl]";let uiRoot;const handleFormatChange=async e=>{const value=e.target.value;try{await repository.setQuestionTextFormat(value);const returnUrlInput=e.target.closest("form").querySelector(SELECTORS_returnUrl),returnUrl=new URL(returnUrlInput.value);await _refresh_ui.default.refresh(uiRoot,returnUrl)}catch(ex){_notification.default.exception(ex)}};_exports.init=uiRootId=>{uiRoot=document.getElementById(uiRootId);document.getElementById(SELECTORS_formatSelectId).addEventListener("change",handleFormatChange)}}));
//# sourceMappingURL=question_text_format.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"question_text_format.min.js","sources":["../src/question_text_format.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 question_text_format question bank control.\n *\n * @module qbank_viewquestiontext/question_text_format\n * @copyright 2023 Catalyst IT Europe Ltd.\n * @author Mark Johnson <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as repository from 'qbank_viewquestiontext/repository';\nimport RefreshUi from 'core_question/refresh_ui';\nimport Notification from 'core/notification';\n\nconst SELECTORS = {\n formatSelectId: 'question-text-format',\n returnUrl: '[name=returnurl]',\n};\n\nlet uiRoot;\n\n/**\n * Save the selected format via a web service call, and refresh the UI.\n *\n * @param {Event} e Select field change event.\n * @return {Promise<void>}\n */\nconst handleFormatChange = async(e) => {\n const value = e.target.value;\n try {\n await repository.setQuestionTextFormat(value);\n const returnUrlInput = e.target.closest('form').querySelector(SELECTORS.returnUrl);\n const returnUrl = new URL(returnUrlInput.value);\n await RefreshUi.refresh(uiRoot, returnUrl);\n } catch (ex) {\n Notification.exception(ex);\n }\n};\n\n/**\n * Initialise question text format widget.\n *\n * Find the uiRoot element and attach a change listener to the question text format selector.\n *\n * @param {String} uiRootId\n */\nexport const init = (uiRootId) => {\n uiRoot = document.getElementById(uiRootId);\n const select = document.getElementById(SELECTORS.formatSelectId);\n select.addEventListener('change', handleFormatChange);\n};\n"],"names":["SELECTORS","uiRoot","handleFormatChange","async","value","e","target","repository","setQuestionTextFormat","returnUrlInput","closest","querySelector","returnUrl","URL","RefreshUi","refresh","ex","exception","uiRootId","document","getElementById","addEventListener"],"mappings":";;;;;;;;4HA4BMA,yBACc,uBADdA,oBAES,uBAGXC,aAQEC,mBAAqBC,MAAAA,UACjBC,MAAQC,EAAEC,OAAOF,gBAEbG,WAAWC,sBAAsBJ,aACjCK,eAAiBJ,EAAEC,OAAOI,QAAQ,QAAQC,cAAcX,qBACxDY,UAAY,IAAIC,IAAIJ,eAAeL,aACnCU,oBAAUC,QAAQd,OAAQW,WAClC,MAAOI,0BACQC,UAAUD,oBAWVE,WACjBjB,OAASkB,SAASC,eAAeF,UAClBC,SAASC,eAAepB,0BAChCqB,iBAAiB,SAAUnB"}
@@ -0,0 +1,3 @@
define("qbank_viewquestiontext/repository",["exports","core/ajax"],(function(_exports,_ajax){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setQuestionTextFormat=void 0;_exports.setQuestionTextFormat=format=>(0,_ajax.call)([{methodname:"qbank_viewquestiontext_set_question_text_format",args:{format:format}}])[0]}));
//# sourceMappingURL=repository.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"repository.min.js","sources":["../src/repository.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 * External function calls for qbank_columnsortorder\n *\n * @module qbank_viewquestiontext/repository\n * @copyright 2023 Catalyst IT Europe Ltd.\n * @author Mark Johnson <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\n\n/**\n * Set the question text format.\n *\n * @param {Number} format The question text format.\n * @return {Promise}\n */\nexport const setQuestionTextFormat = (format) => fetchMany([{\n methodname: 'qbank_viewquestiontext_set_question_text_format',\n args: {\n format,\n },\n}])[0];\n"],"names":["format","methodname","args"],"mappings":"0NAgCsCA,SAAW,cAAU,CAAC,CACxDC,WAAY,kDACZC,KAAM,CACFF,OAAAA,WAEJ"}
@@ -0,0 +1,65 @@
// 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 question_text_format question bank control.
*
* @module qbank_viewquestiontext/question_text_format
* @copyright 2023 Catalyst IT Europe Ltd.
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as repository from 'qbank_viewquestiontext/repository';
import RefreshUi from 'core_question/refresh_ui';
import Notification from 'core/notification';
const SELECTORS = {
formatSelectId: 'question-text-format',
returnUrl: '[name=returnurl]',
};
let uiRoot;
/**
* Save the selected format via a web service call, and refresh the UI.
*
* @param {Event} e Select field change event.
* @return {Promise<void>}
*/
const handleFormatChange = async(e) => {
const value = e.target.value;
try {
await repository.setQuestionTextFormat(value);
const returnUrlInput = e.target.closest('form').querySelector(SELECTORS.returnUrl);
const returnUrl = new URL(returnUrlInput.value);
await RefreshUi.refresh(uiRoot, returnUrl);
} catch (ex) {
Notification.exception(ex);
}
};
/**
* Initialise question text format widget.
*
* Find the uiRoot element and attach a change listener to the question text format selector.
*
* @param {String} uiRootId
*/
export const init = (uiRootId) => {
uiRoot = document.getElementById(uiRootId);
const select = document.getElementById(SELECTORS.formatSelectId);
select.addEventListener('change', handleFormatChange);
};
@@ -14,15 +14,25 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Filter managing question text display.
* External function calls for qbank_columnsortorder
*
* @module qbank_viewquestiontext/datafilter/filtertypes/showtext
* @author 2022 Ghaly Marc-Alexandre <marc-alexandreghaly@catalyst-ca.net>
* @copyright 2022 Catalyst IT Australia Pty Ltd
* @module qbank_viewquestiontext/repository
* @copyright 2023 Catalyst IT Europe Ltd.
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import GenericFilter from 'core/datafilter/filtertype';
import {call as fetchMany} from 'core/ajax';
export default class extends GenericFilter {
}
/**
* Set the question text format.
*
* @param {Number} format The question text format.
* @return {Promise}
*/
export const setQuestionTextFormat = (format) => fetchMany([{
methodname: 'qbank_viewquestiontext_set_question_text_format',
args: {
format,
},
}])[0];
@@ -0,0 +1,79 @@
<?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_viewquestiontext\external;
use context_system;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
use qbank_viewquestiontext\output\question_text_format;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/editlib.php');
/**
* External function for setting the question text format.
*
* @package qbank_viewquestiontext
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class set_question_text_format extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'format' => new external_value(PARAM_INT, 'Format for the question text', VALUE_REQUIRED),
]);
}
/**
* Returns description of method result value.
*/
public static function execute_returns(): void {
}
/**
* Save the question text format preference for the current user.
*
* @param int $format Format for the question text.
*/
public static function execute(int $format): void {
[
'format' => $format,
] = self::validate_parameters(
self::execute_parameters(),
[
'format' => $format,
]
);
if (!in_array($format, [question_text_format::OFF, question_text_format::PLAIN, question_text_format::FULL])) {
throw new \invalid_parameter_exception('$format must be one of question_text_format::OFF, ::PLAIN or ::FULL.');
}
$context = context_system::instance();
self::validate_context($context);
\question_set_or_get_user_preference('qbshowtext', $format, 0, new \moodle_url('/'));
}
}
@@ -0,0 +1,92 @@
<?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_viewquestiontext\output;
use core_question\local\bank\view;
use qbank_viewquestiontext\question_text_row;
use renderer_base;
/**
* Question text format selector.
*
* @package qbank_viewquestiontext
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_text_format implements \renderable, \templatable {
/**
* @var int Question text is off.
*/
const OFF = 0;
/**
* @var int Question text is displayed in plain text mode.
*/
const PLAIN = 1;
/**
* @var int Question text is displayed fully rendered.
*/
const FULL = 2;
/** @var int|mixed The current display preference value. */
protected int $preference;
/**
* @var \moodle_url The return URL for redirecting back to the current question bank page.
*/
protected \moodle_url $returnurl;
/**
* Store the returnurl and the current preference value.
*
* @param view $qbank
* @throws \moodle_exception
*/
public function __construct(view $qbank) {
$row = new question_text_row($qbank);
$this->returnurl = new \moodle_url($qbank->returnurl);
$this->preference = question_get_display_preference($row->get_preference_key(), 0, PARAM_INT, new \moodle_url(''));
}
public function export_for_template(renderer_base $output): array {
return [
'formaction' => new \moodle_url('/question/bank/viewquestiontext/save.php'),
'sesskey' => sesskey(),
'returnurl' => $this->returnurl->out(false),
'options' => [
(object)[
'label' => get_string('showquestiontext_off', 'question'),
'value' => self::OFF,
'selected' => $this->preference === self::OFF,
],
(object)[
'label' => get_string('showquestiontext_plain', 'question'),
'value' => self::PLAIN,
'selected' => $this->preference === self::PLAIN,
],
(object)[
'label' => get_string('showquestiontext_full', 'question'),
'value' => self::FULL,
'selected' => $this->preference === self::FULL,
],
],
'label' => get_string('showquestiontext', 'core_question'),
];
}
}
@@ -16,8 +16,10 @@
namespace qbank_viewquestiontext;
use core\context;
use core_question\local\bank\plugin_features_base;
use core_question\local\bank\view;
use qbank_viewquestiontext\output\question_text_format;
/**
* Class columns is the entrypoint for the columns.
@@ -29,15 +31,27 @@ use core_question\local\bank\view;
*/
class plugin_feature extends plugin_features_base {
public function get_question_columns($qbank): array {
return [
new question_text_row($qbank)
];
/**
* Return an additional row for displaying the question text, if user has a preference set to display it.
*
* @param view $qbank
* @return array
*/
public function get_question_columns(view $qbank): array {
$row = new question_text_row($qbank);
$preference = (int)question_get_display_preference($row->get_preference_key(), '0', PARAM_INT, new \moodle_url('/'));
if ($preference != question_text_format::OFF) {
return [
$row,
];
}
return [];
}
public function get_question_filters(view $qbank = null): array {
public function get_question_bank_controls(view $qbank, context $context, int $categoryid): array {
return [
new questiontext_condition($qbank),
400 => new question_text_format($qbank),
];
}
}
@@ -17,6 +17,7 @@
namespace qbank_viewquestiontext;
use core_question\local\bank\row_base;
use qbank_viewquestiontext\output\question_text_format;
use question_utils;
/**
@@ -29,8 +30,8 @@ use question_utils;
*/
class question_text_row extends row_base {
/** @var bool if true, we will show the question text reduced to plain text, else it is fully rendered. */
protected $plain;
/** @var int if true, we will show the question text reduced to plain text, else it is fully rendered. */
protected $preference;
/** @var \stdClass $formatoptions options used when displaying the question text as HTML. */
protected $formatoptions;
@@ -38,8 +39,7 @@ class question_text_row extends row_base {
protected function init(): void {
// Cannot use $this->get_preference because of PHP type hints.
$preference = question_get_display_preference($this->get_preference_key(), 0, PARAM_INT, new \moodle_url(''));
$this->plain = 1 === (int) $preference;
$this->preference = (int)question_get_display_preference($this->get_preference_key(), 0, PARAM_INT, new \moodle_url(''));
$this->formatoptions = new \stdClass();
$this->formatoptions->noclean = true;
$this->formatoptions->para = false;
@@ -55,14 +55,12 @@ class question_text_row extends row_base {
protected function display_content($question, $rowclasses): void {
// Access 'showtext' filter from pagevars.
$display = $this->qbank->get_pagevars('filter')['showtext'] ?? null;
if ($display) {
$showtext = (int)$display['values'][0];
if ($this->preference !== question_text_format::OFF) {
$text = '';
if ($showtext === questiontext_condition::PLAIN) {
if ($this->preference === question_text_format::PLAIN) {
$text = question_utils::to_plain_text($question->questiontext,
$question->questiontextformat, ['noclean' => true, 'para' => false, 'filter' => false]);
} else if ($showtext === questiontext_condition::FULL) {
} else if ($this->preference === question_text_format::FULL) {
$text = question_rewrite_question_preview_urls($question->questiontext, $question->id,
$question->contextid, 'question', 'questiontext', $question->id,
$question->contextid, 'core_question');
@@ -1,108 +0,0 @@
<?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_viewquestiontext;
use core\output\datafilter;
use core_question\local\bank\condition;
use core_question\local\bank\view;
/**
* This class controls from which category questions are listed.
*
* @package qbank_viewquestiontext
* @copyright 2022 Catalyst IT Australia Pty Ltd
* @author Ghaly Marc-Alexandre <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class questiontext_condition extends condition {
/**
* @var int Question text is off.
*/
const OFF = 0;
/**
* @var int Question text is displayed in plain text mode.
*/
const PLAIN = 1;
/**
* @var int Question text is displayed fully rendered.
*/
const FULL = 2;
/**
* @var ?int $showtext Initial value of the filter, for determining the pre-selected option.
*/
protected ?int $showtext = null;
public function __construct(view $qbank = null) {
if (is_null($qbank)) {
return;
}
$filter = $qbank->get_pagevars('filter');
if (isset($filter['showtext'])) {
$this->showtext = (int)$filter['showtext']['values'][0];
}
}
public function allow_custom() {
return false;
}
public function allow_multiple() {
return false;
}
public static function get_condition_key() {
return 'showtext';
}
public function get_title() {
return get_string('showquestiontext', 'core_question');
}
public function get_join_list(): array {
return [
datafilter::JOINTYPE_ANY,
];
}
public function get_filter_class() {
return 'qbank_viewquestiontext/datafilter/filtertypes/showtext';
}
public function get_initial_values() {
return [
[
'value' => self::OFF,
'title' => get_string('showquestiontext_off', 'question'),
'selected' => $this->showtext === self::OFF,
],
[
'value' => self::PLAIN,
'title' => get_string('showquestiontext_plain', 'question'),
'selected' => $this->showtext === self::PLAIN,
],
[
'value' => self::FULL,
'title' => get_string('showquestiontext_full', 'question'),
'selected' => $this->showtext === self::FULL,
]
];
}
}
@@ -0,0 +1,36 @@
<?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/>.
/**
* qbank_viewquestiontext external functions and service definitions.
*
* @package qbank_viewquestiontext
* @category webservice
* @copyright 2023 Catalyst IT Europe Ltd
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$functions = [
'qbank_viewquestiontext_set_question_text_format' => [
'classname' => 'qbank_viewquestiontext\external\set_question_text_format',
'description' => 'Sets the preference for displaying and formatting the question text',
'type' => 'write',
'ajax' => true,
],
];
+48
View File
@@ -0,0 +1,48 @@
<?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/>.
/**
* Synchronously save the question text display preference, and redirect back to the previous page.
*
* This is progressively enhanced by question_text_format.js, but this remains as a fallback.
*
* @package qbank_viewquestiontext
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->dirroot . '/question/editlib.php');
$format = required_param('format', PARAM_INT);
$returnurl = required_param('returnurl', PARAM_LOCALURL);
require_login();
require_sesskey();
$validformats = [
\qbank_viewquestiontext\output\question_text_format::OFF,
\qbank_viewquestiontext\output\question_text_format::PLAIN,
\qbank_viewquestiontext\output\question_text_format::FULL,
];
if (!in_array($format, $validformats)) {
throw new \invalid_parameter_exception('$format must be one of question_text_format::OFF, ::PLAIN or ::FULL.');
}
question_set_or_get_user_preference('qbshowtext', $format, 0, new \moodle_url('/'));
redirect(new moodle_url($returnurl));
@@ -0,0 +1,3 @@
.jsenabled .question-text-format .input-group-append {
display: none;
}
@@ -0,0 +1,58 @@
{{!
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_viewquestiontext/question_text_format
Example context (json):
{
"label": "Question text format",
"options": [
{
"value": 1,
"label": "Plain text",
"selected": true
},
{
"value": 2,
"label": "Fully rendered",
"selected": false
}
]
}
}}
<form class="question-text-format ml-1" action="{{formaction}}">
<input type="hidden" name="sesskey" value="{{sesskey}}">
<input type="hidden" name="returnurl" value="{{returnurl}}">
<div class="input-group">
<label class="input-group-prepend" for="question-text-format">
<span class="input-group-text">{{label}}</span>
</label>
<select class="form-control" name="format" id="question-text-format">
{{#options}}
<option value="{{value}}"{{#selected}} selected{{/selected}}>{{label}}</option>
{{/options}}
</select>
<div class="input-group-append">
<button class="btn btn-outline-dark mb-2">Save</button>
</div>
</div>
</form>
{{#js}}
require(['qbank_viewquestiontext/question_text_format'], QuestionTextFormat => {
QuestionTextFormat.init('questionscontainer');
});
{{/js}}
@@ -19,19 +19,19 @@ Feature: Use the qbank plugin manager page for viewquestiontext
@javascript
Scenario: Display of plain question text can be turned on and off
When I am on the "Test quiz" "mod_quiz > question bank" page logged in as admin
And I apply question bank filter "Show question text in the question list?" with value "text only"
And I set the field "Show question text in the question list?" to "text only"
Then I should see "Answer the first question"
And ".totestforhtml" "css_element" should not exist in the "Answer the first question" "table_row"
And I apply question bank filter "Show question text in the question list?" with value "No"
And I set the field "Show question text in the question list?" to "No"
And I should not see "Answer the first question"
@javascript
Scenario: Display of full question text can be turned on and off
When I am on the "Test quiz" "mod_quiz > question bank" page logged in as admin
And I apply question bank filter "Show question text in the question list?" with value "with images"
And I set the field "Show question text in the question list?" to "with images"
Then I should see "Answer the first question"
And ".totestforhtml" "css_element" should exist in the "Answer the first question" "table_row"
And I apply question bank filter "Show question text in the question list?" with value "No"
And I set the field "Show question text in the question list?" to "No"
And I should not see "Answer the first question"
@javascript
@@ -53,5 +53,5 @@ Feature: Use the qbank plugin manager page for viewquestiontext
And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
And I click on "Enable" "link" in the "View question text" "table_row"
And I am on the "Test quiz" "mod_quiz > question bank" page
When I apply question bank filter "Show question text in the question list?" with value "text only"
When I set the field "Show question text in the question list?" to "text only"
And I should see "Answer the first question"
+1 -1
View File
@@ -26,6 +26,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'qbank_viewquestiontext';
$plugin->version = 2023042400;
$plugin->version = 2023042401;
$plugin->requires = 2023041800;
$plugin->maturity = MATURITY_STABLE;
@@ -52,4 +52,7 @@ class question_type_column extends column_base {
return 'q.qtype';
}
public function get_default_width(): int {
return 45;
}
}

Some files were not shown because too many files have changed in this diff Show More