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 03b241bc149..8ee30c9f1ba 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'] = [