mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 16:32:18 +02:00
Merge branch 'MDL-84900_main' of https://github.com/marxjohnson/moodle
This commit is contained in:
commit
ac92443315
@ -268,6 +268,7 @@ $string['numqas'] = 'No. question attempts';
|
||||
$string['numquestions'] = 'No. questions';
|
||||
$string['numquestionsandhidden'] = '{$a->numquestions} (+{$a->numhidden} hidden +{$a->numdraft} draft)';
|
||||
$string['otherquestionbank'] = 'Other question banks';
|
||||
$string['otherquestionbankstoomany'] = '> {$a} results, please refine your search.';
|
||||
$string['page-question-x'] = 'Any question page';
|
||||
$string['page-question-edit'] = 'Question editing page';
|
||||
$string['page-question-category'] = 'Question category page';
|
||||
|
@ -3294,6 +3294,12 @@ $functions = array(
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
],
|
||||
'core_question_search_shared_banks' => [
|
||||
'classname' => '\core_question\external\search_shared_banks',
|
||||
'description' => 'Get a list of shared question banks filtered by a search term.',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
],
|
||||
'core_message_set_unsent_message' => [
|
||||
'classname' => 'core_message\external\set_unsent_message',
|
||||
'description' => 'Store an unsent message string',
|
||||
|
2
mod/quiz/amd/build/add_question_modal.min.js
vendored
2
mod/quiz/amd/build/add_question_modal.min.js
vendored
@ -5,6 +5,6 @@ define("mod_quiz/add_question_modal",["exports","core/modal","core/fragment","co
|
||||
* @module mod_quiz/add_question_modal
|
||||
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=_interopRequireDefault(_modal),Fragment=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}(Fragment),_formAutocomplete=_interopRequireDefault(_formAutocomplete);class AddQuestionModal extends _modal.default{configure(modalConfig){modalConfig.large=!0,modalConfig.show=!0,modalConfig.removeOnClose=!0,this.setContextId(modalConfig.contextId),this.setAddOnPageId(modalConfig.addOnPage),this.quizCmId=modalConfig.quizCmId,this.bankCmId=modalConfig.bankCmId,this.originalTitle=modalConfig.title,super.configure(modalConfig)}constructor(root){super(root),this.contextId=null,this.addOnPageId=null}setContextId(id){this.contextId=id}getContextId(){return this.contextId}setAddOnPageId(id){this.addOnPageId=id}getAddOnPageId(){return this.addOnPageId}async handleSwitchBankContentReload(Selector){var _document$querySelect;this.setTitle((0,_str.getString)("selectquestionbank","mod_quiz"));const el=document.createElement("button");el.classList.add("btn","btn-primary"),el.textContent=await(0,_str.getString)("gobacktoquiz","mod_quiz"),el.setAttribute("data-action","go-back"),el.setAttribute("value",this.bankCmId),this.setFooter(el),this.setBody(Fragment.loadFragment("mod_quiz","switch_question_bank",this.getContextId(),{quizcmid:this.quizCmId,bankcmid:this.bankCmId}));const placeholder=await(0,_str.getString)("searchbyname","mod_quiz");return await this.getBodyPromise(),await _formAutocomplete.default.enhance(Selector,!1,"",placeholder,!1,!0,"",!0),null===(_document$querySelect=document.querySelector(".search-banks .form-autocomplete-selection"))||void 0===_document$querySelect||_document$querySelect.classList.add("d-none"),this}}return _exports.default=AddQuestionModal,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=_interopRequireDefault(_modal),Fragment=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}(Fragment),_formAutocomplete=_interopRequireDefault(_formAutocomplete);class AddQuestionModal extends _modal.default{configure(modalConfig){modalConfig.large=!0,modalConfig.show=!0,modalConfig.removeOnClose=!0,this.setContextId(modalConfig.contextId),this.setAddOnPageId(modalConfig.addOnPage),this.quizCmId=modalConfig.quizCmId,this.bankCmId=modalConfig.bankCmId,this.originalTitle=modalConfig.title,super.configure(modalConfig)}constructor(root){super(root),this.contextId=null,this.addOnPageId=null}setContextId(id){this.contextId=id}getContextId(){return this.contextId}setAddOnPageId(id){this.addOnPageId=id}getAddOnPageId(){return this.addOnPageId}async handleSwitchBankContentReload(Selector){var _document$querySelect;this.setTitle((0,_str.getString)("selectquestionbank","mod_quiz"));const el=document.createElement("button");el.classList.add("btn","btn-primary"),el.textContent=await(0,_str.getString)("gobacktoquiz","mod_quiz"),el.setAttribute("data-action","go-back"),el.setAttribute("value",this.bankCmId),this.setFooter(el),this.setBody(Fragment.loadFragment("mod_quiz","switch_question_bank",this.getContextId(),{quizcmid:this.quizCmId,bankcmid:this.bankCmId}));const placeholder=await(0,_str.getString)("searchbyname","mod_quiz");return await this.getBodyPromise(),await _formAutocomplete.default.enhance(Selector,!1,"core_question/question_banks_datasource",placeholder,!1,!0,"",!0),null===(_document$querySelect=document.querySelector(".search-banks .form-autocomplete-selection"))||void 0===_document$querySelect||_document$querySelect.classList.add("d-none"),this}}return _exports.default=AddQuestionModal,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=add_question_modal.min.js.map
|
File diff suppressed because one or more lines are too long
@ -132,7 +132,7 @@ export default class AddQuestionModal extends Modal {
|
||||
await AutoComplete.enhance(
|
||||
Selector,
|
||||
false,
|
||||
'',
|
||||
'core_question/question_banks_datasource',
|
||||
placeholder,
|
||||
false,
|
||||
true,
|
||||
|
11
question/amd/build/question_banks_datasource.min.js
vendored
Normal file
11
question/amd/build/question_banks_datasource.min.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
define("core_question/question_banks_datasource",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){var obj;
|
||||
/**
|
||||
* Autocomplete data source for shared question banks.
|
||||
*
|
||||
* @module core_question/question_banks_datasource
|
||||
* @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @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.default=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};var _default={transport:function(selector,query,callback){const contextId=document.querySelector(selector).dataset.contextid;if(!contextId)throw new Error("The attribute data-contextid is required on "+selector);(0,_ajax.call)([{methodname:"core_question_search_shared_banks",args:{contextid:contextId,search:query}}])[0].then(callback).catch(_notification.default.exception)},processResults:(selector,results)=>results.sharedbanks};return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=question_banks_datasource.min.js.map
|
1
question/amd/build/question_banks_datasource.min.js.map
Normal file
1
question/amd/build/question_banks_datasource.min.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"question_banks_datasource.min.js","sources":["../src/question_banks_datasource.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 * Autocomplete data source for shared question banks.\n *\n * @module core_question/question_banks_datasource\n * @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}\n * @author Mark Johnson <mark.johnson@catalyst-eu.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport Notification from 'core/notification';\n\nexport default {\n\n transport: function(selector, query, callback) {\n const element = document.querySelector(selector);\n const contextId = element.dataset.contextid;\n\n if (!contextId) {\n throw new Error('The attribute data-contextid is required on ' + selector);\n }\n\n fetchMany([{\n methodname: 'core_question_search_shared_banks',\n args: {\n contextid: contextId,\n search: query,\n },\n }])[0]\n .then(callback)\n .catch(Notification.exception);\n },\n\n processResults: (selector, results) => {\n return results.sharedbanks;\n },\n};\n"],"names":["transport","selector","query","callback","contextId","document","querySelector","dataset","contextid","Error","methodname","args","search","then","catch","Notification","exception","processResults","results","sharedbanks"],"mappings":";;;;;;;;sKA2Be,CAEXA,UAAW,SAASC,SAAUC,MAAOC,gBAE3BC,UADUC,SAASC,cAAcL,UACbM,QAAQC,cAE7BJ,gBACK,IAAIK,MAAM,+CAAiDR,yBAG3D,CAAC,CACPS,WAAY,oCACZC,KAAM,CACFH,UAAWJ,UACXQ,OAAQV,UAEZ,GACHW,KAAKV,UACLW,MAAMC,sBAAaC,YAGxBC,eAAgB,CAAChB,SAAUiB,UAChBA,QAAQC"}
|
52
question/amd/src/question_banks_datasource.js
Normal file
52
question/amd/src/question_banks_datasource.js
Normal file
@ -0,0 +1,52 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Autocomplete data source for shared question banks.
|
||||
*
|
||||
* @module core_question/question_banks_datasource
|
||||
* @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {call as fetchMany} from 'core/ajax';
|
||||
import Notification from 'core/notification';
|
||||
|
||||
export default {
|
||||
|
||||
transport: function(selector, query, callback) {
|
||||
const element = document.querySelector(selector);
|
||||
const contextId = element.dataset.contextid;
|
||||
|
||||
if (!contextId) {
|
||||
throw new Error('The attribute data-contextid is required on ' + selector);
|
||||
}
|
||||
|
||||
fetchMany([{
|
||||
methodname: 'core_question_search_shared_banks',
|
||||
args: {
|
||||
contextid: contextId,
|
||||
search: query,
|
||||
},
|
||||
}])[0]
|
||||
.then(callback)
|
||||
.catch(Notification.exception);
|
||||
},
|
||||
|
||||
processResults: (selector, results) => {
|
||||
return results.sharedbanks;
|
||||
},
|
||||
};
|
122
question/classes/external/search_shared_banks.php
vendored
Normal file
122
question/classes/external/search_shared_banks.php
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
<?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 core_question\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_multiple_structure;
|
||||
use core_external\external_value;
|
||||
use core_question\local\bank\question_bank_helper;
|
||||
use core\context;
|
||||
|
||||
/**
|
||||
* Return a filtered of the user's shared question banks
|
||||
*
|
||||
* For use with core_question/question_banks_datasource as a source for autocomplete suggestions.
|
||||
*
|
||||
* @package core_question
|
||||
* @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class search_shared_banks extends external_api {
|
||||
|
||||
/**
|
||||
* @var int The maximum number of banks to return.
|
||||
*/
|
||||
const MAX_RESULTS = 20;
|
||||
|
||||
/**
|
||||
* Define parameters for external function.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'contextid' => new external_value(PARAM_INT, 'The current context ID.'),
|
||||
'search' => new external_value(PARAM_TEXT, 'Search terms by which to filter the shared banks.', default: ''),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return ID and formatted name of question banks accessible by the user, in courses other than the one $contextid is in.
|
||||
*
|
||||
* @param int $contextid Context ID of the current activity
|
||||
* @param string $search String to filter results by question bank name
|
||||
* @return array
|
||||
*/
|
||||
public static function execute(int $contextid, string $search = ''): array {
|
||||
[
|
||||
'contextid' => $contextid,
|
||||
'search' => $search,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'contextid' => $contextid,
|
||||
'search' => $search,
|
||||
]);
|
||||
|
||||
$modulecontext = context::instance_by_id($contextid);
|
||||
$courseid = $modulecontext->get_parent_context()->instanceid;
|
||||
|
||||
$sharedbanks = question_bank_helper::get_activity_instances_with_shareable_questions(
|
||||
notincourseids: [$courseid],
|
||||
havingcap: ['moodle/question:managecategory'],
|
||||
filtercontext: $modulecontext,
|
||||
search: $search,
|
||||
limit: self::MAX_RESULTS + 1, // Return up to 1 extra result, so we know there are more.
|
||||
);
|
||||
|
||||
$suggestions = array_map(
|
||||
fn($sharedbank) => [
|
||||
'value' => $sharedbank->modid,
|
||||
'label' => $sharedbank->coursenamebankname,
|
||||
],
|
||||
$sharedbanks,
|
||||
);
|
||||
|
||||
if (count($suggestions) > self::MAX_RESULTS) {
|
||||
// If there are too many results, replace the last one with a placeholder.
|
||||
$suggestions[array_key_last($suggestions)] = [
|
||||
'value' => 0,
|
||||
'label' => get_string('otherquestionbankstoomany', 'question', self::MAX_RESULTS),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'sharedbanks' => $suggestions,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define return values.
|
||||
*
|
||||
* @return external_single_structure
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'sharedbanks' => new external_multiple_structure(
|
||||
new external_single_structure([
|
||||
'value' => new external_value(PARAM_INT, 'Module ID of the shared bank.'),
|
||||
'label' => new external_value(PARAM_TEXT, 'Formatted bank name'),
|
||||
]),
|
||||
'List of shared banks',
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
@ -134,6 +134,10 @@ class question_bank_helper {
|
||||
* @param bool $getcategories optionally return the categories belonging to these banks.
|
||||
* @param int $currentbankid optionally include the bank id you want included as the first result from the method return.
|
||||
* it will only be included if the other parameters allow it.
|
||||
* @param ?context $filtercontext Optional context to use for all string filtering, useful for performance when calling with
|
||||
* parameters that will get banks across multiple contexts.
|
||||
* @param string $search Optional term to search question bank instances by name
|
||||
* @param int $limit The number of results to return (default 0 = no limit)
|
||||
* @return stdClass[]
|
||||
*/
|
||||
public static function get_activity_instances_with_shareable_questions(
|
||||
@ -142,13 +146,19 @@ class question_bank_helper {
|
||||
array $havingcap = [],
|
||||
bool $getcategories = false,
|
||||
int $currentbankid = 0,
|
||||
?context $filtercontext = null,
|
||||
string $search = '',
|
||||
int $limit = 0,
|
||||
): array {
|
||||
return self::get_bank_instances(true,
|
||||
$incourseids,
|
||||
$notincourseids,
|
||||
$getcategories,
|
||||
$currentbankid,
|
||||
$havingcap
|
||||
$havingcap,
|
||||
$filtercontext,
|
||||
$search,
|
||||
$limit,
|
||||
);
|
||||
}
|
||||
|
||||
@ -161,6 +171,8 @@ class question_bank_helper {
|
||||
* @param bool $getcategories optionally return the categories belonging to these banks.
|
||||
* @param int $currentbankid optionally include the bank id you want included as the first result from the method return.
|
||||
* it will only be included if the other parameters allow it.
|
||||
* @param ?context $filtercontext Optional context to use for all string filtering, useful for performance when calling with
|
||||
* parameters that will get banks across multiple contexts.
|
||||
* @return stdClass[]
|
||||
*/
|
||||
public static function get_activity_instances_with_private_questions(
|
||||
@ -169,13 +181,15 @@ class question_bank_helper {
|
||||
array $havingcap = [],
|
||||
bool $getcategories = false,
|
||||
int $currentbankid = 0,
|
||||
?context $filtercontext = null,
|
||||
): array {
|
||||
return self::get_bank_instances(false,
|
||||
$incourseids,
|
||||
$notincourseids,
|
||||
$getcategories,
|
||||
$currentbankid,
|
||||
$havingcap
|
||||
$havingcap,
|
||||
$filtercontext,
|
||||
);
|
||||
}
|
||||
|
||||
@ -191,6 +205,10 @@ class question_bank_helper {
|
||||
* @param int $currentbankid optionally include the bank id you want included as the first result from the method return.
|
||||
* it will only be included if the other parameters allow it.
|
||||
* @param array $havingcap current user must have these capabilities on each bank context.
|
||||
* @param ?context $filtercontext Optional context to use for all string filtering, useful for performance when calling with
|
||||
* parameters that will get banks across multiple contexts.
|
||||
* @param string $search Optional term to search question bank instances by name
|
||||
* @param int $limit The number of results to return (default 0 = no limit)
|
||||
* @return stdClass[]
|
||||
*/
|
||||
private static function get_bank_instances(
|
||||
@ -200,6 +218,9 @@ class question_bank_helper {
|
||||
bool $getcategories = false,
|
||||
int $currentbankid = 0,
|
||||
array $havingcap = [],
|
||||
?context $filtercontext = null,
|
||||
string $search = '',
|
||||
int $limit = 0,
|
||||
): array {
|
||||
global $DB;
|
||||
|
||||
@ -241,13 +262,17 @@ class question_bank_helper {
|
||||
if ($plugin === self::get_default_question_bank_activity_name()) {
|
||||
$sql .= " AND p{$key}.type <> '" . self::TYPE_PREVIEW . "'";
|
||||
}
|
||||
if (!empty($search)) {
|
||||
$sql .= " AND " . $DB->sql_like("p{$key}.name", ":search{$key}", false);
|
||||
$params["search{$key}"] = "%{$search}%";
|
||||
}
|
||||
$pluginssql[] = $sql;
|
||||
}
|
||||
$pluginssql = implode(' ', $pluginssql);
|
||||
|
||||
// Build the SQL to filter out any requested course ids.
|
||||
if (!empty($notincourseids)) {
|
||||
[$notincoursesql, $notincourseparams] = $DB->get_in_or_equal($notincourseids, SQL_PARAMS_QM, 'param', false);
|
||||
[$notincoursesql, $notincourseparams] = $DB->get_in_or_equal($notincourseids, SQL_PARAMS_NAMED, 'param', false);
|
||||
$notincoursesql = "AND cm.course {$notincoursesql}";
|
||||
$params = array_merge($params, $notincourseparams);
|
||||
} else {
|
||||
@ -256,7 +281,7 @@ class question_bank_helper {
|
||||
|
||||
// Build the SQL to include ONLY records belonging to the requested courses.
|
||||
if (!empty($incourseids)) {
|
||||
[$incoursesql, $incourseparams] = $DB->get_in_or_equal($incourseids);
|
||||
[$incoursesql, $incourseparams] = $DB->get_in_or_equal($incourseids, SQL_PARAMS_NAMED);
|
||||
$incoursesql = " AND cm.course {$incoursesql}";
|
||||
$params = array_merge($params, $incourseparams);
|
||||
} else {
|
||||
@ -265,8 +290,8 @@ class question_bank_helper {
|
||||
|
||||
// Optionally order the results by the requested bank id.
|
||||
if (!empty($currentbankid)) {
|
||||
$orderbysql = " ORDER BY CASE WHEN cm.id = ? THEN 0 ELSE 1 END ASC, cm.id DESC ";
|
||||
$params[] = $currentbankid;
|
||||
$orderbysql = " ORDER BY CASE WHEN cm.id = :currentbankid THEN 0 ELSE 1 END ASC, cm.id DESC ";
|
||||
$params['currentbankid'] = $currentbankid;
|
||||
} else {
|
||||
$orderbysql = '';
|
||||
}
|
||||
@ -280,7 +305,7 @@ class question_bank_helper {
|
||||
GROUP BY cm.id, cm.course
|
||||
{$orderbysql}";
|
||||
|
||||
$rs = $DB->get_recordset_sql($sql, $params);
|
||||
$rs = $DB->get_recordset_sql($sql, $params, limitnum: $limit);
|
||||
$banks = [];
|
||||
|
||||
foreach ($rs as $cm) {
|
||||
@ -293,8 +318,9 @@ class question_bank_helper {
|
||||
}
|
||||
}
|
||||
// Populate the raw record.
|
||||
$banks[] = self::get_formatted_bank($cm, $currentbankid);
|
||||
$banks[] = self::get_formatted_bank($cm, $currentbankid, filtercontext: $filtercontext);
|
||||
}
|
||||
$rs->close();
|
||||
|
||||
return $banks;
|
||||
}
|
||||
@ -305,9 +331,15 @@ class question_bank_helper {
|
||||
*
|
||||
* @param int $userid of the user to get recently viewed banks for.
|
||||
* @param int $notincourseid if supplied don't return any in this course id
|
||||
* @param ?context $filtercontext Optional context to use for all string filtering, useful for performance when calling with
|
||||
* parameters that will get banks across multiple contexts.
|
||||
* @return cm_info[]
|
||||
*/
|
||||
public static function get_recently_used_open_banks(int $userid, int $notincourseid = 0): array {
|
||||
public static function get_recently_used_open_banks(
|
||||
int $userid,
|
||||
int $notincourseid = 0,
|
||||
?context $filtercontext = null,
|
||||
): array {
|
||||
$prefs = get_user_preferences(self::RECENTLY_VIEWED, null, $userid);
|
||||
$contextids = !empty($prefs) ? explode(',', $prefs) : [];
|
||||
if (empty($contextids)) {
|
||||
@ -328,7 +360,7 @@ class question_bank_helper {
|
||||
if (!empty($notincourseid) && $notincourseid == $cm->course) {
|
||||
continue;
|
||||
}
|
||||
$record = self::get_formatted_bank($cm);
|
||||
$record = self::get_formatted_bank($cm, filtercontext: $filtercontext);
|
||||
$banks[] = $record;
|
||||
}
|
||||
|
||||
@ -370,12 +402,15 @@ class question_bank_helper {
|
||||
|
||||
/**
|
||||
* Populate the raw record with data for use in rendering.
|
||||
*
|
||||
* @param stdClass $cm raw course_modules record to populate data from.
|
||||
* @param int $currentbankid set an 'enabled' flag on the instance that matched this id.
|
||||
* Used in qbank_bulkmove/bulk_move.mustache
|
||||
* Used in qbank_bulkmove/bulk_move.mustache
|
||||
* @param ?context $filtercontext Optional context in which to apply filters.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
private static function get_formatted_bank(stdClass $cm, int $currentbankid = 0): stdClass {
|
||||
private static function get_formatted_bank(stdClass $cm, int $currentbankid = 0, ?context $filtercontext = null): stdClass {
|
||||
|
||||
$cminfo = cm_info::create($cm);
|
||||
$concatedcats = !empty($cm->cats) ? explode(self::CATEGORY_SEPARATOR, $cm->cats) : [];
|
||||
@ -390,14 +425,19 @@ class question_bank_helper {
|
||||
}, $concatedcats);
|
||||
|
||||
$bank = new stdClass();
|
||||
$bank->name = $cminfo->get_formatted_name(['escape' => false]);
|
||||
$filteroptions = ['escape' => false];
|
||||
if (!is_null($filtercontext)) {
|
||||
$filteroptions['context'] = $filtercontext;
|
||||
}
|
||||
$bank->name = $cminfo->get_formatted_name($filteroptions);
|
||||
$bank->modid = $cminfo->id;
|
||||
$bank->contextid = $cminfo->context->id;
|
||||
$bank->coursenamebankname = format_string($cminfo->get_course()->shortname, true,
|
||||
['context' => $cminfo->context, 'escape' => false]) . " - {$bank->name}";
|
||||
if (!isset($filteroptions['context'])) {
|
||||
$filteroptions['context'] = context_course::instance($cminfo->get_course()->id);
|
||||
}
|
||||
$bank->coursenamebankname = format_string($cminfo->get_course()->shortname, true, $filteroptions) . " - {$bank->name}";
|
||||
$bank->cminfo = $cminfo;
|
||||
$bank->questioncategories = $categories;
|
||||
|
||||
return $bank;
|
||||
}
|
||||
|
||||
|
@ -62,25 +62,21 @@ class switch_question_bank implements \renderable, \templatable {
|
||||
[, $cm] = get_module_from_cmid($this->quizcmid);
|
||||
$cminfo = cm_info::create($cm);
|
||||
|
||||
$sharedbanks = question_bank_helper::get_activity_instances_with_shareable_questions(
|
||||
notincourseids: [$this->courseid],
|
||||
havingcap: ['moodle/question:managecategory']
|
||||
);
|
||||
$coursesharedbanks = question_bank_helper::get_activity_instances_with_shareable_questions(
|
||||
incourseids: [$this->courseid],
|
||||
havingcap: ['moodle/question:managecategory']
|
||||
havingcap: ['moodle/question:managecategory'],
|
||||
filtercontext: $cminfo->context,
|
||||
);
|
||||
$recentlyviewedbanks = question_bank_helper::get_recently_used_open_banks($this->userid);
|
||||
|
||||
return [
|
||||
'quizname' => $cminfo->get_formatted_name(),
|
||||
'quizcmid' => $this->quizcmid,
|
||||
'quizcontextid' => $cminfo->context->id,
|
||||
'hascoursesharedbanks' => !empty($coursesharedbanks),
|
||||
'coursesharedbanks' => $coursesharedbanks,
|
||||
'hasrecentlyviewedbanks' => !empty($recentlyviewedbanks),
|
||||
'recentlyviewedbanks' => $recentlyviewedbanks,
|
||||
'hassharedbanks' => !empty($sharedbanks),
|
||||
'sharedbanks' => $sharedbanks,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
{
|
||||
"quizname": "Quiz 1",
|
||||
"quizcmid": 1,
|
||||
"quizcontextid": 1,
|
||||
"hascoursesharedbanks": true,
|
||||
"coursesharedbanks": [
|
||||
{
|
||||
@ -131,14 +132,9 @@
|
||||
<hr class="w-75">
|
||||
{{/hasrecentlyviewedbanks}}
|
||||
|
||||
{{#hassharedbanks}}
|
||||
<div class="search-banks">
|
||||
<h5>{{#str}}otherquestionbank, core_question{{/str}}</h5>
|
||||
<select class="form-select" id="searchbanks">
|
||||
<option value=""></option>
|
||||
{{#sharedbanks}}
|
||||
<option value="{{modid}}">{{{coursenamebankname}}}</option>
|
||||
{{/sharedbanks}}
|
||||
</select>
|
||||
</div>
|
||||
{{/hassharedbanks}}
|
||||
|
||||
<div class="search-banks">
|
||||
<h5>{{#str}}otherquestionbank, core_question{{/str}}</h5>
|
||||
<select class="form-select" id="searchbanks" data-contextid="{{quizcontextid}}">
|
||||
</select>
|
||||
</div>
|
||||
|
@ -160,6 +160,54 @@ final class question_bank_helper_test extends \advanced_testcase {
|
||||
$this->assertEquals(1, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* We should be able to filter sharable question bank instances by name.
|
||||
*
|
||||
* @covers ::get_activity_instances_with_shareable_questions
|
||||
* @return void
|
||||
*/
|
||||
public function test_get_instances_by_name(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$user = self::getDataGenerator()->create_user();
|
||||
$roles = $DB->get_records('role', [], '', 'shortname, id');
|
||||
self::setUser($user);
|
||||
|
||||
$sharedmodgen = self::getDataGenerator()->get_plugin_generator('mod_qbank');
|
||||
$category1 = self::getDataGenerator()->create_category();
|
||||
$course1 = self::getDataGenerator()->create_course(['category' => $category1->id]);
|
||||
role_assign($roles['editingteacher']->id, $user->id, \core\context\course::instance($course1->id));
|
||||
|
||||
$sharedmods = [];
|
||||
for ($i = 1; $i <= 21; $i++) {
|
||||
$sharedmods[$i] = $sharedmodgen->create_instance(['course' => $course1, 'name' => "Shared bank {$i}"]);
|
||||
}
|
||||
$sharedmods[22] = $sharedmodgen->create_instance(['course' => $course1, 'name' => "Another bank"]);
|
||||
|
||||
// We get all banks with no parameters.
|
||||
$allsharedbanks = question_bank_helper::get_activity_instances_with_shareable_questions();
|
||||
$this->assertCount(22, $allsharedbanks);
|
||||
|
||||
// Searching for "2", we get the 4 banks with "2" in the name.
|
||||
$twobanks = question_bank_helper::get_activity_instances_with_shareable_questions(search: '2');
|
||||
$this->assertCount(4, $twobanks);
|
||||
$this->assertEquals(
|
||||
[$sharedmods[2]->cmid, $sharedmods[12]->cmid, $sharedmods[20]->cmid, $sharedmods[21]->cmid],
|
||||
array_map(fn($bank) => $bank->modid, $twobanks),
|
||||
);
|
||||
|
||||
// Searching for "Shared bank" with no limit, we should get all 21, but not "Another bank".
|
||||
$sharedbanks = question_bank_helper::get_activity_instances_with_shareable_questions(search: 'Shared bank');
|
||||
$this->assertCount(21, $sharedbanks);
|
||||
$this->assertEmpty(array_filter($sharedbanks, fn($bank) => in_array($bank->name, ['Another bank'])));
|
||||
|
||||
// Searching for "Shared bank" with a limit of 20, we should get all except number 21 and "Another bank".
|
||||
$limitedbanks = question_bank_helper::get_activity_instances_with_shareable_questions(search: 'Shared bank', limit: 20);
|
||||
$this->assertCount(20, $limitedbanks);
|
||||
$this->assertEmpty(array_filter($limitedbanks, fn($bank) => in_array($bank->name, ['Shared bank 21', 'Another bank'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert creating a default mod_qbank instance on a course provides the expected boilerplate settings.
|
||||
*
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2025040100.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2025040100.02; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '5.0beta (Build: 20250401)'; // Human-friendly version name
|
||||
|
Loading…
x
Reference in New Issue
Block a user