From 5728b158e63d2a5bf4ca993ac66dab0febd7a260 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 | 3 +- .../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 | 37 +++++++++++--- .../lang/en/qbank_managecategories.php | 4 +- question/bank/managecategories/lib.php | 40 ++++++++++++++++ .../tests/behat/question_categories.feature | 20 ++++++++ .../tests/privacy/provider_test.php | 48 +++++++++++++++++++ question/classes/local/bank/view.php | 3 +- 11 files changed, 162 insertions(+), 14 deletions(-) create mode 100644 question/bank/managecategories/lib.php create mode 100644 question/bank/managecategories/tests/privacy/provider_test.php diff --git a/mod/quiz/classes/question/bank/custom_view.php b/mod/quiz/classes/question/bank/custom_view.php index 572ba8daa1b..f0354eaf381 100644 --- a/mod/quiz/classes/question/bank/custom_view.php +++ b/mod/quiz/classes/question/bank/custom_view.php @@ -76,7 +76,8 @@ class custom_view extends \core_question\local\bank\view { $params['filter']['category'] = [ 'jointype' => custom_category_condition::JOINTYPE_DEFAULT, 'values' => [$category->id], - 'filteroptions' => ['includesubcategories' => false], + 'filteroptions' => ['includesubcategories' => + get_user_preferences('qbank_managecategories_includesubcategories_filter_default', false)], ]; } } 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 76ea895a741..064f3ab7ab2 100644 --- a/question/bank/managecategories/classes/category_condition.php +++ b/question/bank/managecategories/classes/category_condition.php @@ -68,7 +68,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 de587972b66..106dcf8ec8d 100644 --- a/question/bank/managecategories/classes/privacy/provider.php +++ b/question/bank/managecategories/classes/privacy/provider.php @@ -16,6 +16,9 @@ namespace qbank_managecategories\privacy; +use core_privacy\local\metadata\collection; +use core_privacy\local\request\writer; + /** * Privacy Subsystem for qbank_managecategories implementing null_provider. * @@ -25,14 +28,36 @@ namespace qbank_managecategories\privacy; * @author Guillermo Gomez Arias * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\null_provider { +class provider implements + \core_privacy\local\metadata\provider, + \core_privacy\local\request\user_preference_provider { /** - * Get the language string identifier with the component's language - * file to explain why this plugin stores no data. + * Returns meta data about this system. * - * @return string + * @param collection $collection The initialised collection to add items to. + * @return collection A listing of user data stored through this system. */ - public static function get_reason(): string { - return 'privacy:metadata'; + public static function get_metadata(collection $collection): collection { + $collection->add_user_preference('qbank_managecategories_includesubcategories_filter_default', + 'privacy:preference:includesubcategories_filter_default'); + return $collection; + } + + /** + * Export all user preferences for the plugin. + * + * @param int $userid The userid of the user whose data is to be exported. + */ + public static function export_user_preferences(int $userid) { + $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 2c60dcc732e..2af5628c29a 100644 --- a/question/bank/managecategories/lang/en/qbank_managecategories.php +++ b/question/bank/managecategories/lang/en/qbank_managecategories.php @@ -24,4 +24,6 @@ */ $string['pluginname'] = 'Manage categories'; -$string['privacy:metadata'] = 'The Manage categories question bank plugin does not store any personal data.'; +$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['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.'; diff --git a/question/bank/managecategories/lib.php b/question/bank/managecategories/lib.php new file mode 100644 index 00000000000..c0bc60491ee --- /dev/null +++ b/question/bank/managecategories/lib.php @@ -0,0 +1,40 @@ +. + +/** + * Callbacks for qbank_manageacategories + * + * @package qbank_managecategories + * @copyright 2024 Catalyst IT Europe Ltd + * @author Mark Johnson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Allow update of user preferences via AJAX. + * + * @return array[] + */ +function qbank_managecategories_user_preferences(): array { + return [ + '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 473ad4c80ef..0be05464e23 100644 --- a/question/bank/managecategories/tests/behat/question_categories.feature +++ b/question/bank/managecategories/tests/behat/question_categories.feature @@ -102,3 +102,23 @@ 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" + 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 new file mode 100644 index 00000000000..ab9f0711662 --- /dev/null +++ b/question/bank/managecategories/tests/privacy/provider_test.php @@ -0,0 +1,48 @@ +. + +namespace qbank_managecategories\privacy; + +use advanced_testcase; +use core_privacy\local\request\writer; + +/** + * Unit tests for qbank_managecategories privacy provider. + * + * @package qbank_managecategories + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author 2021, Ghaly Marc-Alexandre + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \qbank_managecategories\privacy\provider + */ +final class provider_test extends advanced_testcase { + /** + * Test to check export_user_preferences. + * + * @covers ::export_user_preferences + */ + public function test_export_user_preferences(): void { + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_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->includesubcategories->value); + $this->assertEquals(get_string('questionsubcategoriesdisplayed', + 'qbank_managecategories'), $prefs->includesubcategories->description); + } +} diff --git a/question/classes/local/bank/view.php b/question/classes/local/bank/view.php index 7ddf1739b21..c2c7eaa0a23 100644 --- a/question/classes/local/bank/view.php +++ b/question/classes/local/bank/view.php @@ -244,7 +244,8 @@ class view { $params['filter']['category'] = [ 'jointype' => category_condition::JOINTYPE_DEFAULT, 'values' => [$category->id], - 'filteroptions' => ['includesubcategories' => false], + 'filteroptions' => ['includesubcategories' => + get_user_preferences('qbank_managecategories_includesubcategories_filter_default', false)], ]; } $params['filter']['hidden'] = [