From a6e4638752ddcca385de60bcc6f85d4c77977419 Mon Sep 17 00:00:00 2001 From: hieuvu Date: Tue, 29 Oct 2024 13:34:42 +0700 Subject: [PATCH] MDL-82542 qbank_managecategories: Add new user preferences Keep the checkbox to show questions from subcategories as a user preference. --- .../classes/question/bank/custom_view.php | 16 +++++--------- .../datafilter/filtertypes/categories.min.js | 2 +- .../filtertypes/categories.min.js.map | 2 +- .../src/datafilter/filtertypes/categories.js | 10 +++++++-- .../classes/category_condition.php | 7 +++++- .../classes/privacy/provider.php | 13 +++++++++++ .../lang/en/qbank_managecategories.php | 3 +++ question/bank/managecategories/lib.php | 6 +++++ .../tests/behat/question_categories.feature | 22 +++++++++++++++++++ .../tests/privacy/provider_test.php | 4 ++++ .../local/bank/filter_condition_manager.php | 3 ++- 11 files changed, 71 insertions(+), 17 deletions(-) diff --git a/mod/quiz/classes/question/bank/custom_view.php b/mod/quiz/classes/question/bank/custom_view.php index d3c627d739a..c7d932b48a0 100644 --- a/mod/quiz/classes/question/bank/custom_view.php +++ b/mod/quiz/classes/question/bank/custom_view.php @@ -32,8 +32,8 @@ use core\output\html_writer; 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\filter_condition_manager; use core_question\local\bank\question_version_status; -use mod_quiz\question\bank\filter\custom_category_condition; require_once($CFG->dirroot . '/mod/quiz/locallib.php'); /** @@ -76,16 +76,10 @@ class custom_view extends \core_question\local\bank\view { public function __construct($contexts, $pageurl, $course, $cm, $params, $extraparams) { // Default filter condition. if (!isset($params['filter'])) { - $params['filter'] = []; - [$categoryid, $contextid] = custom_category_condition::validate_category_param($params['cat']); - if (!is_null($categoryid)) { - $category = custom_category_condition::get_category_record($categoryid, $contextid); - $params['filter']['category'] = [ - 'jointype' => custom_category_condition::JOINTYPE_DEFAULT, - 'values' => [$category->id], - 'filteroptions' => ['includesubcategories' => false], - ]; - } + $params['filter'] = filter_condition_manager::get_default_filter($params['cat']); + // The quiz question bank modal doesn't include a hidden filter option. + // Therefore, the default filter hidden condition is unnecessary. + unset($params['filter']['hidden']); } $this->init_columns($this->wanted_columns(), $this->heading_column()); diff --git a/question/bank/managecategories/amd/build/datafilter/filtertypes/categories.min.js b/question/bank/managecategories/amd/build/datafilter/filtertypes/categories.min.js index 53eaeea6353..b89fb7717f6 100644 --- a/question/bank/managecategories/amd/build/datafilter/filtertypes/categories.min.js +++ b/question/bank/managecategories/amd/build/datafilter/filtertypes/categories.min.js @@ -1,3 +1,3 @@ -define("qbank_managecategories/datafilter/filtertypes/categories",["exports","core/datafilter/filtertype","core/templates"],(function(_exports,_filtertype,_templates){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_filtertype=_interopRequireDefault(_filtertype),_templates=_interopRequireDefault(_templates);class _default extends _filtertype.default{constructor(filterType,rootNode,initialValues){let filterOptions=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{includesubcategories:!1};var obj,key,value;super(filterType,rootNode,initialValues),value={includeSubcategories:"input[name=category-subcategories]"},(key="SELECTORS")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.addSubcategoryCheckbox(filterOptions.includesubcategories)}async addSubcategoryCheckbox(){let checked=arguments.length>0&&void 0!==arguments[0]&&arguments[0];const filterValueNode=this.getFilterValueNode(),{html:html}=await _templates.default.renderForPromise("qbank_managecategories/include_subcategories_checkbox",{checked:checked});filterValueNode.insertAdjacentHTML("afterend",html)}get filterOptions(){return[{name:"includesubcategories",value:this.filterRoot.querySelector(this.SELECTORS.includeSubcategories).checked}]}get filterValue(){return{name:this.name,jointype:this.jointype,values:this.values,filteroptions:this.filterOptions}}}return _exports.default=_default,_exports.default})); +define("qbank_managecategories/datafilter/filtertypes/categories",["exports","core/datafilter/filtertype","core/templates","core_user/repository"],(function(_exports,_filtertype,_templates,_repository){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_filtertype=_interopRequireDefault(_filtertype),_templates=_interopRequireDefault(_templates);class _default extends _filtertype.default{constructor(filterType,rootNode,initialValues){let filterOptions=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{includesubcategories:!1};var obj,key,value;super(filterType,rootNode,initialValues),value={includeSubcategories:"input[name=category-subcategories]"},(key="SELECTORS")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.addSubcategoryCheckbox(filterOptions.includesubcategories)}async addSubcategoryCheckbox(){let checked=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;const filterValueNode=this.getFilterValueNode();null==checked?checked=await(0,_repository.getUserPreference)("qbank_managecategories_includesubcategories_filter_default"):(0,_repository.setUserPreference)("qbank_managecategories_includesubcategories_filter_default",checked);const{html:html}=await _templates.default.renderForPromise("qbank_managecategories/include_subcategories_checkbox",{checked:checked&&"0"!==checked});filterValueNode.insertAdjacentHTML("afterend",html)}get filterOptions(){return[{name:"includesubcategories",value:this.filterRoot.querySelector(this.SELECTORS.includeSubcategories).checked}]}get filterValue(){return{name:this.name,jointype:this.jointype,values:this.values,filteroptions:this.filterOptions}}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=categories.min.js.map \ No newline at end of file diff --git a/question/bank/managecategories/amd/build/datafilter/filtertypes/categories.min.js.map b/question/bank/managecategories/amd/build/datafilter/filtertypes/categories.min.js.map index f3818305f07..90d10b81a7e 100644 --- a/question/bank/managecategories/amd/build/datafilter/filtertypes/categories.min.js.map +++ b/question/bank/managecategories/amd/build/datafilter/filtertypes/categories.min.js.map @@ -1 +1 @@ -{"version":3,"file":"categories.min.js","sources":["../../../src/datafilter/filtertypes/categories.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 .\n\n/**\n * Filter managing display of subcategories questions.\n *\n * @module qbank_managecategories/datafilter/filtertypes/categories\n * @author Mark Johnson \n * @copyright 2023 Catalyst IT Europe Ltd.\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport GenericFilter from 'core/datafilter/filtertype';\nimport Templates from 'core/templates';\n\nexport default class extends GenericFilter {\n\n SELECTORS = {\n includeSubcategories: 'input[name=category-subcategories]',\n };\n\n /**\n * Construct a new categoires filter\n *\n * @param {String} filterType The type of filter that this relates to (categories)\n * @param {HTMLElement} rootNode The root node for the participants filterset\n * @param {Array} initialValues The currently selected category IDs.\n * @param {Object} filterOptions An object containing the additional options for the filter, currently \"includesubcategories\"\n * is supported, which if true will display the \"Also show questions from subcategories\" checkbox as checked.\n */\n constructor(filterType, rootNode, initialValues, filterOptions = {includesubcategories: false}) {\n super(filterType, rootNode, initialValues);\n this.addSubcategoryCheckbox(filterOptions.includesubcategories);\n }\n\n async addSubcategoryCheckbox(checked = false) {\n const filterValueNode = this.getFilterValueNode();\n const {html} = await Templates.renderForPromise('qbank_managecategories/include_subcategories_checkbox', {\n checked: checked,\n });\n filterValueNode.insertAdjacentHTML('afterend', html);\n }\n\n get filterOptions() {\n return [\n {name: 'includesubcategories', value: this.filterRoot.querySelector(this.SELECTORS.includeSubcategories).checked}\n ];\n }\n\n get filterValue() {\n return {\n name: this.name,\n jointype: this.jointype,\n values: this.values,\n filteroptions: this.filterOptions,\n };\n }\n}\n"],"names":["GenericFilter","constructor","filterType","rootNode","initialValues","filterOptions","includesubcategories","includeSubcategories","addSubcategoryCheckbox","checked","filterValueNode","this","getFilterValueNode","html","Templates","renderForPromise","insertAdjacentHTML","name","value","filterRoot","querySelector","SELECTORS","filterValue","jointype","values","filteroptions"],"mappings":"8bA2B6BA,oBAezBC,YAAYC,WAAYC,SAAUC,mBAAeC,qEAAgB,CAACC,sBAAsB,2BAC9EJ,WAAYC,SAAUC,qBAdpB,CACRG,qBAAsB,+KAcjBC,uBAAuBH,cAAcC,yDAGjBG,sEACnBC,gBAAkBC,KAAKC,sBACvBC,KAACA,YAAcC,mBAAUC,iBAAiB,wDAAyD,CACrGN,QAASA,UAEbC,gBAAgBM,mBAAmB,WAAYH,MAG/CR,0BACO,CACH,CAACY,KAAM,uBAAwBC,MAAOP,KAAKQ,WAAWC,cAAcT,KAAKU,UAAUd,sBAAsBE,UAI7Ga,wBACO,CACHL,KAAMN,KAAKM,KACXM,SAAUZ,KAAKY,SACfC,OAAQb,KAAKa,OACbC,cAAed,KAAKN"} \ No newline at end of file +{"version":3,"file":"categories.min.js","sources":["../../../src/datafilter/filtertypes/categories.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 .\n\n/**\n * Filter managing display of subcategories questions.\n *\n * @module qbank_managecategories/datafilter/filtertypes/categories\n * @author Mark Johnson \n * @copyright 2023 Catalyst IT Europe Ltd.\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport GenericFilter from 'core/datafilter/filtertype';\nimport Templates from 'core/templates';\nimport {getUserPreference, setUserPreference} from 'core_user/repository';\n\nexport default class extends GenericFilter {\n\n SELECTORS = {\n includeSubcategories: 'input[name=category-subcategories]',\n };\n\n /**\n * Construct a new categoires filter\n *\n * @param {String} filterType The type of filter that this relates to (categories)\n * @param {HTMLElement} rootNode The root node for the participants filterset\n * @param {Array} initialValues The currently selected category IDs.\n * @param {Object} filterOptions An object containing the additional options for the filter, currently \"includesubcategories\"\n * is supported, which if true will display the \"Also show questions from subcategories\" checkbox as checked.\n */\n constructor(filterType, rootNode, initialValues, filterOptions = {includesubcategories: false}) {\n super(filterType, rootNode, initialValues);\n this.addSubcategoryCheckbox(filterOptions.includesubcategories);\n }\n\n async addSubcategoryCheckbox(checked = null) {\n const filterValueNode = this.getFilterValueNode();\n if (checked === null || checked === undefined) {\n checked = await getUserPreference('qbank_managecategories_includesubcategories_filter_default');\n } else {\n setUserPreference('qbank_managecategories_includesubcategories_filter_default', checked);\n }\n const {html} = await Templates.renderForPromise('qbank_managecategories/include_subcategories_checkbox', {\n checked: checked && checked !== '0',\n });\n filterValueNode.insertAdjacentHTML('afterend', html);\n }\n\n get filterOptions() {\n return [\n {name: 'includesubcategories', value: this.filterRoot.querySelector(this.SELECTORS.includeSubcategories).checked}\n ];\n }\n\n get filterValue() {\n return {\n name: this.name,\n jointype: this.jointype,\n values: this.values,\n filteroptions: this.filterOptions,\n };\n }\n}\n"],"names":["GenericFilter","constructor","filterType","rootNode","initialValues","filterOptions","includesubcategories","includeSubcategories","addSubcategoryCheckbox","checked","filterValueNode","this","getFilterValueNode","html","Templates","renderForPromise","insertAdjacentHTML","name","value","filterRoot","querySelector","SELECTORS","filterValue","jointype","values","filteroptions"],"mappings":"ieA4B6BA,oBAezBC,YAAYC,WAAYC,SAAUC,mBAAeC,qEAAgB,CAACC,sBAAsB,2BAC9EJ,WAAYC,SAAUC,qBAdpB,CACRG,qBAAsB,+KAcjBC,uBAAuBH,cAAcC,yDAGjBG,+DAAU,WAC7BC,gBAAkBC,KAAKC,qBACzBH,MAAAA,QACAA,cAAgB,iCAAkB,gGAEhB,6DAA8DA,eAE9EI,KAACA,YAAcC,mBAAUC,iBAAiB,wDAAyD,CACrGN,QAASA,SAAuB,MAAZA,UAExBC,gBAAgBM,mBAAmB,WAAYH,MAG/CR,0BACO,CACH,CAACY,KAAM,uBAAwBC,MAAOP,KAAKQ,WAAWC,cAAcT,KAAKU,UAAUd,sBAAsBE,UAI7Ga,wBACO,CACHL,KAAMN,KAAKM,KACXM,SAAUZ,KAAKY,SACfC,OAAQb,KAAKa,OACbC,cAAed,KAAKN"} \ No newline at end of file diff --git a/question/bank/managecategories/amd/src/datafilter/filtertypes/categories.js b/question/bank/managecategories/amd/src/datafilter/filtertypes/categories.js index 29a439a1c8f..a023f59590c 100644 --- a/question/bank/managecategories/amd/src/datafilter/filtertypes/categories.js +++ b/question/bank/managecategories/amd/src/datafilter/filtertypes/categories.js @@ -24,6 +24,7 @@ import GenericFilter from 'core/datafilter/filtertype'; import Templates from 'core/templates'; +import {getUserPreference, setUserPreference} from 'core_user/repository'; export default class extends GenericFilter { @@ -45,10 +46,15 @@ export default class extends GenericFilter { this.addSubcategoryCheckbox(filterOptions.includesubcategories); } - async addSubcategoryCheckbox(checked = false) { + async addSubcategoryCheckbox(checked = null) { const filterValueNode = this.getFilterValueNode(); + if (checked === null || checked === undefined) { + checked = await getUserPreference('qbank_managecategories_includesubcategories_filter_default'); + } else { + setUserPreference('qbank_managecategories_includesubcategories_filter_default', checked); + } const {html} = await Templates.renderForPromise('qbank_managecategories/include_subcategories_checkbox', { - checked: checked, + checked: checked && checked !== '0', }); filterValueNode.insertAdjacentHTML('afterend', html); } diff --git a/question/bank/managecategories/classes/category_condition.php b/question/bank/managecategories/classes/category_condition.php index a8d82c98a52..7172b9221eb 100644 --- a/question/bank/managecategories/classes/category_condition.php +++ b/question/bank/managecategories/classes/category_condition.php @@ -74,7 +74,12 @@ class category_condition extends condition { $this->category = self::get_category_record($categoryid, $contextid); parent::__construct($qbank); - $this->includesubcategories = $this->filter['filteroptions']['includesubcategories'] ?? false; + if (isset($this->filter['filteroptions']['includesubcategories'])) { + set_user_preference('qbank_managecategories_includesubcategories_filter_default', + $this->filter['filteroptions']['includesubcategories']); + } + $this->includesubcategories = $this->filter['filteroptions']['includesubcategories'] ?? + get_user_preferences('qbank_managecategories_includesubcategories_filter_default', false); } /** diff --git a/question/bank/managecategories/classes/privacy/provider.php b/question/bank/managecategories/classes/privacy/provider.php index ecb1959f354..9f49e789e14 100644 --- a/question/bank/managecategories/classes/privacy/provider.php +++ b/question/bank/managecategories/classes/privacy/provider.php @@ -39,6 +39,8 @@ class provider implements */ public static function get_metadata(collection $collection): collection { $collection->add_user_preference('qbank_managecategories_showdescriptions', 'privacy:preference:showdescriptions'); + $collection->add_user_preference('qbank_managecategories_includesubcategories_filter_default', + 'privacy:preference:includesubcategories_filter_default'); return $collection; } @@ -58,5 +60,16 @@ class provider implements get_string($displaydescription, 'qbank_managecategories') ); } + + $includesubcategories = get_user_preferences('qbank_managecategories_includesubcategories_filter_default', null, $userid); + if (isset($includesubcategories)) { + $displayquestions = $includesubcategories ? 'questionsubcategoriesdisplayed' : 'questionsubcategoriesnotdisplayed'; + writer::export_user_preference( + 'qbank_managecategories', + 'includesubcategories', + $includesubcategories, + get_string($displayquestions, 'qbank_managecategories') + ); + } } } diff --git a/question/bank/managecategories/lang/en/qbank_managecategories.php b/question/bank/managecategories/lang/en/qbank_managecategories.php index aecb6f18fa1..2ff60b51d11 100644 --- a/question/bank/managecategories/lang/en/qbank_managecategories.php +++ b/question/bank/managecategories/lang/en/qbank_managecategories.php @@ -35,5 +35,8 @@ $string['lastcategoryinthiscontext'] = 'This category is the only one in this co $string['movecategory'] = 'Move {$a}'; $string['newchild'] = 'As new child of {$a}'; $string['pluginname'] = 'Manage categories'; +$string['privacy:preference:includesubcategories_filter_default'] = 'Indicates whether or not the "Also show questions from subcategories" filter option is enabled by default when the user views the question bank.'; $string['privacy:preference:showdescriptions'] = 'A flag to indicate whether or not category descriptions are shown.'; +$string['questionsubcategoriesdisplayed'] = 'When selecting categories in the question bank, the "Also show questions from subcategories" option is used and are automatically included in the filter results.'; +$string['questionsubcategoriesnotdisplayed'] = 'When selecting categories in the question bank, the "Also show questions from subcategories" option is not used, so questions from subcategories are not included in the filter results.'; $string['showcategorydescription'] = 'Show descriptions'; diff --git a/question/bank/managecategories/lib.php b/question/bank/managecategories/lib.php index f1190dccc26..6c2856a65d7 100644 --- a/question/bank/managecategories/lib.php +++ b/question/bank/managecategories/lib.php @@ -36,5 +36,11 @@ function qbank_managecategories_user_preferences(): array { 'default' => false, 'permissioncallback' => [core_user::class, 'is_current_user'], ], + 'qbank_managecategories_includesubcategories_filter_default' => [ + 'type' => PARAM_BOOL, + 'null' => NULL_NOT_ALLOWED, + 'default' => false, + 'permissioncallback' => [core_user::class, 'is_current_user'], + ], ]; } diff --git a/question/bank/managecategories/tests/behat/question_categories.feature b/question/bank/managecategories/tests/behat/question_categories.feature index e75e5077617..4a1e8071a69 100644 --- a/question/bank/managecategories/tests/behat/question_categories.feature +++ b/question/bank/managecategories/tests/behat/question_categories.feature @@ -113,3 +113,25 @@ Feature: A teacher can put questions in categories in the question bank When I reload the page Then I should see "Question 1" in the "categoryquestions" "table" And the field "Also show questions from subcategories" matches value "1" + And I am on the "Course 1" "core_question > course question bank" page + And the field "Also show questions from subcategories" matches value "1" + + Scenario: Filter question by category and subcategories in Quiz question page + Given the following "activities" exist: + | activity | name | course | idnumber | + | quiz | Test quiz | C1 | quiz1 | + And I am on the "Test quiz" "mod_quiz > Edit" page + And I open the "last" add to quiz menu + And I follow "from question bank" + And I click on "Switch bank" "button" + And I click on "Qbank 1" "link" in the "Select question bank" "dialogue" + When I set the field "Also show questions from subcategories" to "1" + And I click on "Apply filters" "button" + Then I should see "Question 1" in the "categoryquestions" "table" + And I set the field "Also show questions from subcategories" to "0" + And I click on "Apply filters" "button" + And I should not see "Question 1" + And I click on "Close" "button" in the "Add from the question bank at the end" "dialogue" + And I open the "last" add to quiz menu + And I follow "from question bank" + And the field "Also show questions from subcategories" matches value "0" diff --git a/question/bank/managecategories/tests/privacy/provider_test.php b/question/bank/managecategories/tests/privacy/provider_test.php index c7c5a2aab11..ea4b6c414f4 100644 --- a/question/bank/managecategories/tests/privacy/provider_test.php +++ b/question/bank/managecategories/tests/privacy/provider_test.php @@ -39,10 +39,14 @@ final class provider_test extends advanced_testcase { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); set_user_preference('qbank_managecategories_showdescriptions', 1, $user); + set_user_preference('qbank_managecategories_includesubcategories_filter_default', 1, $user); provider::export_user_preferences($user->id); $writer = writer::with_context(\context_system::instance()); $prefs = $writer->get_user_preferences('qbank_managecategories'); $this->assertEquals(1, $prefs->showdescr->value); + $this->assertEquals(1, $prefs->includesubcategories->value); $this->assertEquals(get_string('displaydescription', 'qbank_managecategories'), $prefs->showdescr->description); + $this->assertEquals(get_string('questionsubcategoriesdisplayed', + 'qbank_managecategories'), $prefs->includesubcategories->description); } } diff --git a/question/classes/local/bank/filter_condition_manager.php b/question/classes/local/bank/filter_condition_manager.php index 739e467cf22..9530f0b8e8e 100644 --- a/question/classes/local/bank/filter_condition_manager.php +++ b/question/classes/local/bank/filter_condition_manager.php @@ -136,7 +136,8 @@ class filter_condition_manager { $filter['category'] = [ 'jointype' => condition::JOINTYPE_DEFAULT, 'values' => [$category->id], - 'filteroptions' => ['includesubcategories' => false], + 'filteroptions' => ['includesubcategories' => + get_user_preferences('qbank_managecategories_includesubcategories_filter_default', false)], ]; } $filter['hidden'] = [