diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php
index b8da2e822d1df..b2f328df7a4ed 100644
--- a/lib/classes/plugin_manager.php
+++ b/lib/classes/plugin_manager.php
@@ -1943,6 +1943,7 @@ public static function standard_plugins_list($type) {
'editquestion',
'exportquestions',
'importquestions',
+ 'tagquestion',
'viewcreator',
'viewquestionname',
'viewquestiontext',
diff --git a/question/amd/build/edit_tags.min.js b/question/amd/build/edit_tags.min.js
index 785d7adb0737b..68e4d566f4eb1 100644
--- a/question/amd/build/edit_tags.min.js
+++ b/question/amd/build/edit_tags.min.js
@@ -1,2 +1,2 @@
-define ("core_question/edit_tags",["jquery","core/fragment","core/str","core/modal_events","core/modal_factory","core/notification","core/custom_interaction_events","core_question/repository","core_question/selectors"],function(a,b,c,d,e,f,g,h,i){var j=function(a){a.find(i.actions.save).prop("disabled",!1)},k=function(a){a.find(i.actions.save).prop("disabled",!0)},l=function(a){return a.getBody().find("form").serialize()},m=function(a){var b=a.find(i.containers.loadingIcon);b.removeClass("hidden")},n=function(a){var b=a.find(i.containers.loadingIcon);b.addClass("hidden")},o=function(a,b){a.getBody().attr("data-contextid",b)},p=function(a){return a.getBody().data("contextid")},q=function(a,b){a.getBody().attr("data-questionid",b)},r=function(a){return a.getBody().data("questionid")},s=function(h){var l=e.create({type:e.types.SAVE_CANCEL,large:!1},[h,i.actions.edittags]).then(function(a){c.get_string("questiontags","question").then(function(b){a.setTitle(b);return b}).fail(f.exception);a.getRoot().on(d.save,function(b){var c=a.getBody().find("form");c.submit();b.preventDefault()});a.getRoot().on("submit","form",function(b){t(a,h).then(function(){a.hide();location.reload()}).fail(f.exception);b.preventDefault();b.stopPropagation()});return a});h.on(g.events.activate,i.actions.edittags,function(c){var d=a(c.currentTarget),e=d.data("questionid"),g=!!d.data("cantag"),p=d.data("contextid");l.then(function(a){k(h);m(h);var c=b.loadFragment("question","tags_form",p,{id:e});a.setBody(c);c.then(function(){j(h)}).always(function(){n(h)}).fail(f.exception);if(g){a.getRoot().find(i.actions.save).show()}else{a.getRoot().find(i.actions.save).hide()}q(a,e);o(a,p);return a}).fail(f.exception);c.preventDefault()})},t=function(a,b){k(b);m(b);var c=l(a),d=r(a),e=p(a);return h.submitTagCreateUpdateForm(d,e,c).always(function(){n(b);j(b)}).fail(f.exception)};return{init:function init(b){b=a(b);s(b)}}});
+define ("core_question/edit_tags",["jquery","core/fragment","core/str","core/modal_events","core/modal_factory","core/notification","core/custom_interaction_events","core_question/repository","core_question/selectors"],function(a,b,c,d,e,f,g,h,i){var j=function(a){a.find(i.actions.save).prop("disabled",!1)},k=function(a){a.find(i.actions.save).prop("disabled",!0)},l=function(a){return a.getBody().find("form").serialize()},m=function(a){var b=a.find(i.containers.loadingIcon);b.removeClass("hidden")},n=function(a){var b=a.find(i.containers.loadingIcon);b.addClass("hidden")},o=function(a,b){a.getBody().attr("data-contextid",b)},p=function(a){return a.getBody().data("contextid")},q=function(a,b){a.getBody().attr("data-questionid",b)},r=function(a){return a.getBody().data("questionid")},s=function(h){var l=e.create({type:e.types.SAVE_CANCEL,large:!1},[h,i.actions.edittags]).then(function(a){c.get_string("questiontags","question").then(function(b){a.setTitle(b);return b}).fail(f.exception);a.getRoot().on(d.save,function(b){var c=a.getBody().find("form");c.submit();b.preventDefault()});a.getRoot().on("submit","form",function(b){t(a,h).then(function(){a.hide();location.reload()}).fail(f.exception);b.preventDefault();b.stopPropagation()});return a});h.on(g.events.activate,i.actions.edittags,function(c){var d=a(c.currentTarget),e=d.data("questionid"),g=!!d.data("cantag"),p=d.data("contextid");l.then(function(a){k(h);m(h);var c=b.loadFragment("question","tags_form",p,{id:e});a.setBody(c);c.then(function(){j(h)}).always(function(){n(h)}).fail(f.exception);if(g){a.getRoot().find(i.actions.save).show()}else{a.getRoot().find(i.actions.save).hide()}q(a,e);o(a,p);return a}).fail(f.exception);c.preventDefault()})},t=function(a,b){k(b);m(b);var c=l(a),d=r(a),e=p(a);return h.submitTagCreateUpdateForm(d,e,c).always(function(){n(b);j(b)}).fail(f.exception)};return{init:function init(b){window.console.warn("warn: The core_question/repository has been deprecated.Please use qbank_tagquestion/repository instead.");b=a(b);s(b)}}});
//# sourceMappingURL=edit_tags.min.js.map
diff --git a/question/amd/build/edit_tags.min.js.map b/question/amd/build/edit_tags.min.js.map
index 0952d5dad3ebe..219ac72622b75 100644
--- a/question/amd/build/edit_tags.min.js.map
+++ b/question/amd/build/edit_tags.min.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../src/edit_tags.js"],"names":["define","$","Fragment","Str","ModalEvents","ModalFactory","Notification","CustomEvents","Repository","QuestionSelectors","enableSaveButton","root","find","actions","save","prop","disableSaveButton","getFormData","modal","getBody","serialize","startLoading","loadingIconContainer","containers","loadingIcon","removeClass","stopLoading","addClass","setContextId","contextId","attr","getContextId","data","setQuestionId","questionId","getQuestionId","registerEventListeners","modalPromise","create","type","types","SAVE_CANCEL","large","edittags","then","get_string","string","setTitle","fail","exception","getRoot","on","e","form","submit","preventDefault","hide","location","reload","stopPropagation","events","activate","currentTarget","canTag","tagsFragment","loadFragment","id","setBody","always","show","formData","submitTagCreateUpdateForm","init"],"mappings":"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","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 * A javascript module to handle question tags editing.\n *\n * @module core_question/edit_tags\n * @copyright 2018 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/fragment',\n 'core/str',\n 'core/modal_events',\n 'core/modal_factory',\n 'core/notification',\n 'core/custom_interaction_events',\n 'core_question/repository',\n 'core_question/selectors',\n ],\n function(\n $,\n Fragment,\n Str,\n ModalEvents,\n ModalFactory,\n Notification,\n CustomEvents,\n Repository,\n QuestionSelectors\n ) {\n\n /**\n * Enable the save button in the footer.\n *\n * @param {object} root The container element.\n * @method enableSaveButton\n */\n var enableSaveButton = function(root) {\n root.find(QuestionSelectors.actions.save).prop('disabled', false);\n };\n\n /**\n * Disable the save button in the footer.\n *\n * @param {object} root The container element.\n * @method disableSaveButton\n */\n var disableSaveButton = function(root) {\n root.find(QuestionSelectors.actions.save).prop('disabled', true);\n };\n\n /**\n * Get the serialised form data.\n *\n * @method getFormData\n * @param {object} modal The modal object.\n * @return {string} serialised form data\n */\n var getFormData = function(modal) {\n return modal.getBody().find('form').serialize();\n };\n\n /**\n * Set the element state to loading.\n *\n * @param {object} root The container element\n * @method startLoading\n */\n var startLoading = function(root) {\n var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);\n\n loadingIconContainer.removeClass('hidden');\n };\n\n /**\n * Remove the loading state from the element.\n *\n * @param {object} root The container element\n * @method stopLoading\n */\n var stopLoading = function(root) {\n var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);\n\n loadingIconContainer.addClass('hidden');\n };\n\n /**\n * Set the context Id data attribute on the modal.\n *\n * @param {Promise} modal The modal promise.\n * @param {int} contextId The context id.\n */\n var setContextId = function(modal, contextId) {\n modal.getBody().attr('data-contextid', contextId);\n };\n\n /**\n * Get the context Id data attribute value from the modal body.\n *\n * @param {Promise} modal The modal promise.\n * @return {int} The context id.\n */\n var getContextId = function(modal) {\n return modal.getBody().data('contextid');\n };\n\n /**\n * Set the question Id data attribute on the modal.\n *\n * @param {Promise} modal The modal promise.\n * @param {int} questionId The question Id.\n */\n var setQuestionId = function(modal, questionId) {\n modal.getBody().attr('data-questionid', questionId);\n };\n\n /**\n * Get the question Id data attribute value from the modal body.\n *\n * @param {Promise} modal The modal promise.\n * @return {int} The question Id.\n */\n var getQuestionId = function(modal) {\n return modal.getBody().data('questionid');\n };\n\n /**\n * Register event listeners for the module.\n *\n * @param {object} root The calendar root element\n */\n var registerEventListeners = function(root) {\n var modalPromise = ModalFactory.create(\n {\n type: ModalFactory.types.SAVE_CANCEL,\n large: false\n },\n [root, QuestionSelectors.actions.edittags]\n ).then(function(modal) {\n // All of this code only executes once, when the modal is\n // first created. This allows us to add any code that should\n // only be run once, such as adding event handlers to the modal.\n Str.get_string('questiontags', 'question')\n .then(function(string) {\n modal.setTitle(string);\n return string;\n })\n .fail(Notification.exception);\n\n modal.getRoot().on(ModalEvents.save, function(e) {\n var form = modal.getBody().find('form');\n form.submit();\n e.preventDefault();\n });\n\n modal.getRoot().on('submit', 'form', function(e) {\n save(modal, root).then(function() {\n modal.hide();\n location.reload();\n return;\n }).fail(Notification.exception);\n\n // Stop the form from actually submitting and prevent it's\n // propagation because we have already handled the event.\n e.preventDefault();\n e.stopPropagation();\n });\n\n return modal;\n });\n\n // We need to add an event handler to the tags link because there are\n // multiple links on the page and without adding a listener we don't know\n // which one the user clicked on the show the modal.\n root.on(CustomEvents.events.activate, QuestionSelectors.actions.edittags, function(e) {\n var currentTarget = $(e.currentTarget);\n\n var questionId = currentTarget.data('questionid'),\n canTag = !!currentTarget.data('cantag'),\n contextId = currentTarget.data('contextid');\n\n // This code gets called each time the user clicks the tag link\n // so we can use it to reload the contents of the tag modal.\n modalPromise.then(function(modal) {\n // Display spinner and disable save button.\n disableSaveButton(root);\n startLoading(root);\n\n var args = {\n id: questionId\n };\n\n var tagsFragment = Fragment.loadFragment('question', 'tags_form', contextId, args);\n modal.setBody(tagsFragment);\n\n tagsFragment.then(function() {\n enableSaveButton(root);\n return;\n })\n .always(function() {\n // Always hide the loading spinner when the request\n // has completed.\n stopLoading(root);\n return;\n })\n .fail(Notification.exception);\n\n // Show or hide the save button depending on whether the user\n // has the capability to edit the tags.\n if (canTag) {\n modal.getRoot().find(QuestionSelectors.actions.save).show();\n } else {\n modal.getRoot().find(QuestionSelectors.actions.save).hide();\n }\n\n setQuestionId(modal, questionId);\n setContextId(modal, contextId);\n\n return modal;\n }).fail(Notification.exception);\n\n e.preventDefault();\n });\n };\n\n /**\n * Send the form data to the server to save question tags.\n *\n * @method save\n * @param {object} modal The modal object.\n * @param {object} root The container element.\n * @return {object} A promise\n */\n var save = function(modal, root) {\n // Display spinner and disable save button.\n disableSaveButton(root);\n startLoading(root);\n\n var formData = getFormData(modal);\n var questionId = getQuestionId(modal);\n var contextId = getContextId(modal);\n\n // Send the form data to the server for processing.\n return Repository.submitTagCreateUpdateForm(questionId, contextId, formData)\n .always(function() {\n // Regardless of success or error we should always stop\n // the loading icon and re-enable the buttons.\n stopLoading(root);\n enableSaveButton(root);\n return;\n })\n .fail(Notification.exception);\n };\n\n return {\n init: function(root) {\n root = $(root);\n registerEventListeners(root);\n }\n };\n});\n"],"file":"edit_tags.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../src/edit_tags.js"],"names":["define","$","Fragment","Str","ModalEvents","ModalFactory","Notification","CustomEvents","Repository","QuestionSelectors","enableSaveButton","root","find","actions","save","prop","disableSaveButton","getFormData","modal","getBody","serialize","startLoading","loadingIconContainer","containers","loadingIcon","removeClass","stopLoading","addClass","setContextId","contextId","attr","getContextId","data","setQuestionId","questionId","getQuestionId","registerEventListeners","modalPromise","create","type","types","SAVE_CANCEL","large","edittags","then","get_string","string","setTitle","fail","exception","getRoot","on","e","form","submit","preventDefault","hide","location","reload","stopPropagation","events","activate","currentTarget","canTag","tagsFragment","loadFragment","id","setBody","always","show","formData","submitTagCreateUpdateForm","init","window","console","warn"],"mappings":"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","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 * A javascript module to handle question tags editing.\n *\n * @deprecated since Moodle 4.0\n * @todo Final deprecation on Moodle 4.4 MDL-72438\n * @module core_question/edit_tags\n * @copyright 2018 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/fragment',\n 'core/str',\n 'core/modal_events',\n 'core/modal_factory',\n 'core/notification',\n 'core/custom_interaction_events',\n 'core_question/repository',\n 'core_question/selectors',\n ],\n function(\n $,\n Fragment,\n Str,\n ModalEvents,\n ModalFactory,\n Notification,\n CustomEvents,\n Repository,\n QuestionSelectors\n ) {\n\n /**\n * Enable the save button in the footer.\n *\n * @param {object} root The container element.\n * @method enableSaveButton\n */\n var enableSaveButton = function(root) {\n root.find(QuestionSelectors.actions.save).prop('disabled', false);\n };\n\n /**\n * Disable the save button in the footer.\n *\n * @param {object} root The container element.\n * @method disableSaveButton\n */\n var disableSaveButton = function(root) {\n root.find(QuestionSelectors.actions.save).prop('disabled', true);\n };\n\n /**\n * Get the serialised form data.\n *\n * @method getFormData\n * @param {object} modal The modal object.\n * @return {string} serialised form data\n */\n var getFormData = function(modal) {\n return modal.getBody().find('form').serialize();\n };\n\n /**\n * Set the element state to loading.\n *\n * @param {object} root The container element\n * @method startLoading\n */\n var startLoading = function(root) {\n var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);\n\n loadingIconContainer.removeClass('hidden');\n };\n\n /**\n * Remove the loading state from the element.\n *\n * @param {object} root The container element\n * @method stopLoading\n */\n var stopLoading = function(root) {\n var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);\n\n loadingIconContainer.addClass('hidden');\n };\n\n /**\n * Set the context Id data attribute on the modal.\n *\n * @param {Promise} modal The modal promise.\n * @param {int} contextId The context id.\n */\n var setContextId = function(modal, contextId) {\n modal.getBody().attr('data-contextid', contextId);\n };\n\n /**\n * Get the context Id data attribute value from the modal body.\n *\n * @param {Promise} modal The modal promise.\n * @return {int} The context id.\n */\n var getContextId = function(modal) {\n return modal.getBody().data('contextid');\n };\n\n /**\n * Set the question Id data attribute on the modal.\n *\n * @param {Promise} modal The modal promise.\n * @param {int} questionId The question Id.\n */\n var setQuestionId = function(modal, questionId) {\n modal.getBody().attr('data-questionid', questionId);\n };\n\n /**\n * Get the question Id data attribute value from the modal body.\n *\n * @param {Promise} modal The modal promise.\n * @return {int} The question Id.\n */\n var getQuestionId = function(modal) {\n return modal.getBody().data('questionid');\n };\n\n /**\n * Register event listeners for the module.\n *\n * @param {object} root The calendar root element\n */\n var registerEventListeners = function(root) {\n var modalPromise = ModalFactory.create(\n {\n type: ModalFactory.types.SAVE_CANCEL,\n large: false\n },\n [root, QuestionSelectors.actions.edittags]\n ).then(function(modal) {\n // All of this code only executes once, when the modal is\n // first created. This allows us to add any code that should\n // only be run once, such as adding event handlers to the modal.\n Str.get_string('questiontags', 'question')\n .then(function(string) {\n modal.setTitle(string);\n return string;\n })\n .fail(Notification.exception);\n\n modal.getRoot().on(ModalEvents.save, function(e) {\n var form = modal.getBody().find('form');\n form.submit();\n e.preventDefault();\n });\n\n modal.getRoot().on('submit', 'form', function(e) {\n save(modal, root).then(function() {\n modal.hide();\n location.reload();\n return;\n }).fail(Notification.exception);\n\n // Stop the form from actually submitting and prevent it's\n // propagation because we have already handled the event.\n e.preventDefault();\n e.stopPropagation();\n });\n\n return modal;\n });\n\n // We need to add an event handler to the tags link because there are\n // multiple links on the page and without adding a listener we don't know\n // which one the user clicked on the show the modal.\n root.on(CustomEvents.events.activate, QuestionSelectors.actions.edittags, function(e) {\n var currentTarget = $(e.currentTarget);\n\n var questionId = currentTarget.data('questionid'),\n canTag = !!currentTarget.data('cantag'),\n contextId = currentTarget.data('contextid');\n\n // This code gets called each time the user clicks the tag link\n // so we can use it to reload the contents of the tag modal.\n modalPromise.then(function(modal) {\n // Display spinner and disable save button.\n disableSaveButton(root);\n startLoading(root);\n\n var args = {\n id: questionId\n };\n\n var tagsFragment = Fragment.loadFragment('question', 'tags_form', contextId, args);\n modal.setBody(tagsFragment);\n\n tagsFragment.then(function() {\n enableSaveButton(root);\n return;\n })\n .always(function() {\n // Always hide the loading spinner when the request\n // has completed.\n stopLoading(root);\n return;\n })\n .fail(Notification.exception);\n\n // Show or hide the save button depending on whether the user\n // has the capability to edit the tags.\n if (canTag) {\n modal.getRoot().find(QuestionSelectors.actions.save).show();\n } else {\n modal.getRoot().find(QuestionSelectors.actions.save).hide();\n }\n\n setQuestionId(modal, questionId);\n setContextId(modal, contextId);\n\n return modal;\n }).fail(Notification.exception);\n\n e.preventDefault();\n });\n };\n\n /**\n * Send the form data to the server to save question tags.\n *\n * @method save\n * @param {object} modal The modal object.\n * @param {object} root The container element.\n * @return {object} A promise\n */\n var save = function(modal, root) {\n // Display spinner and disable save button.\n disableSaveButton(root);\n startLoading(root);\n\n var formData = getFormData(modal);\n var questionId = getQuestionId(modal);\n var contextId = getContextId(modal);\n\n // Send the form data to the server for processing.\n return Repository.submitTagCreateUpdateForm(questionId, contextId, formData)\n .always(function() {\n // Regardless of success or error we should always stop\n // the loading icon and re-enable the buttons.\n stopLoading(root);\n enableSaveButton(root);\n return;\n })\n .fail(Notification.exception);\n };\n\n return {\n init: function(root) {\n window.console.warn('warn: The core_question/repository has been deprecated.' +\n 'Please use qbank_tagquestion/repository instead.');\n root = $(root);\n registerEventListeners(root);\n }\n };\n});\n"],"file":"edit_tags.min.js"}
\ No newline at end of file
diff --git a/question/amd/build/repository.min.js b/question/amd/build/repository.min.js
index 0d9f41eafd736..b0773a68d96f9 100644
--- a/question/amd/build/repository.min.js
+++ b/question/amd/build/repository.min.js
@@ -1,2 +1,2 @@
-define ("core_question/repository",["jquery","core/ajax"],function(a,b){return{submitTagCreateUpdateForm:function submitTagCreateUpdateForm(a,c,d){return b.call([{methodname:"core_question_submit_tags_form",args:{questionid:a,contextid:c,formdata:d}}])[0]}}});
+define ("core_question/repository",["jquery","core/ajax"],function(a,b){return{submitTagCreateUpdateForm:function submitTagCreateUpdateForm(a,c,d){window.console.warn("warn: The core_question/repository has been deprecated.Please use qbank_tagquestion/repository instead.");return b.call([{methodname:"core_question_submit_tags_form",args:{questionid:a,contextid:c,formdata:d}}])[0]}}});
//# sourceMappingURL=repository.min.js.map
diff --git a/question/amd/build/repository.min.js.map b/question/amd/build/repository.min.js.map
index 6ab5340c9dae3..b858f96ede520 100644
--- a/question/amd/build/repository.min.js.map
+++ b/question/amd/build/repository.min.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../src/repository.js"],"names":["define","$","Ajax","submitTagCreateUpdateForm","questionId","contextId","formdata","call","methodname","args","questionid","contextid"],"mappings":"AAsBAA,OAAM,4BAAC,CAAC,QAAD,CAAW,WAAX,CAAD,CAA0B,SAASC,CAAT,CAAYC,CAAZ,CAAkB,CAsB9C,MAAO,CACHC,yBAAyB,CAdG,QAA5BA,CAAAA,yBAA4B,CAASC,CAAT,CAAqBC,CAArB,CAAgCC,CAAhC,CAA0C,CAUtE,MAAOJ,CAAAA,CAAI,CAACK,IAAL,CAAU,CATH,CACVC,UAAU,CAAE,gCADF,CAEVC,IAAI,CAAE,CACFC,UAAU,CAAEN,CADV,CAEFO,SAAS,CAAEN,CAFT,CAGFC,QAAQ,CAAEA,CAHR,CAFI,CASG,CAAV,EAAqB,CAArB,CACV,CAEM,CAGV,CAzBK,CAAN","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 * A javascript module to handle question ajax actions.\n *\n * @module core_question/repository\n * @copyright 2017 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax'], function($, Ajax) {\n\n /**\n * Submit the form data for the question tags form.\n *\n * @method submitTagCreateUpdateForm\n * @param {string} formdata The URL encoded values from the form\n * @return {promise}\n */\n var submitTagCreateUpdateForm = function(questionId, contextId, formdata) {\n var request = {\n methodname: 'core_question_submit_tags_form',\n args: {\n questionid: questionId,\n contextid: contextId,\n formdata: formdata\n }\n };\n\n return Ajax.call([request])[0];\n };\n\n return {\n submitTagCreateUpdateForm: submitTagCreateUpdateForm\n };\n});\n"],"file":"repository.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../src/repository.js"],"names":["define","$","Ajax","submitTagCreateUpdateForm","questionId","contextId","formdata","window","console","warn","call","methodname","args","questionid","contextid"],"mappings":"AAwBAA,OAAM,4BAAC,CAAC,QAAD,CAAW,WAAX,CAAD,CAA0B,SAASC,CAAT,CAAYC,CAAZ,CAAkB,CAwB9C,MAAO,CACHC,yBAAyB,CAhBG,QAA5BA,CAAAA,yBAA4B,CAASC,CAAT,CAAqBC,CAArB,CAAgCC,CAAhC,CAA0C,CACtEC,MAAM,CAACC,OAAP,CAAeC,IAAf,4GAWA,MAAOP,CAAAA,CAAI,CAACQ,IAAL,CAAU,CATH,CACVC,UAAU,CAAE,gCADF,CAEVC,IAAI,CAAE,CACFC,UAAU,CAAET,CADV,CAEFU,SAAS,CAAET,CAFT,CAGFC,QAAQ,CAAEA,CAHR,CAFI,CASG,CAAV,EAAqB,CAArB,CACV,CAEM,CAGV,CA3BK,CAAN","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 * A javascript module to handle question ajax actions.\n *\n * @deprecated since Moodle 4.0\n * @todo Final deprecation on Moodle 4.4 MDL-72438\n * @module core_question/repository\n * @copyright 2017 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax'], function($, Ajax) {\n\n /**\n * Submit the form data for the question tags form.\n *\n * @method submitTagCreateUpdateForm\n * @param {string} formdata The URL encoded values from the form\n * @return {promise}\n */\n var submitTagCreateUpdateForm = function(questionId, contextId, formdata) {\n window.console.warn('warn: The core_question/repository has been deprecated.' +\n 'Please use qbank_tagquestion/repository instead.');\n var request = {\n methodname: 'core_question_submit_tags_form',\n args: {\n questionid: questionId,\n contextid: contextId,\n formdata: formdata\n }\n };\n\n return Ajax.call([request])[0];\n };\n\n return {\n submitTagCreateUpdateForm: submitTagCreateUpdateForm\n };\n});\n"],"file":"repository.min.js"}
\ No newline at end of file
diff --git a/question/amd/build/selectors.min.js b/question/amd/build/selectors.min.js
index 51fef64a1abf2..31f9a9cf3b792 100644
--- a/question/amd/build/selectors.min.js
+++ b/question/amd/build/selectors.min.js
@@ -1,2 +1,2 @@
-define ("core_question/selectors",[],function(){return{actions:{save:"[data-action=\"save\"]",edittags:"[data-action=\"edittags\"]"},containers:{loadingIcon:"[data-region=\"overlay-icon-container\"]"}}});
+define ("core_question/selectors",[],function(){window.console.warn("warn: The core_question/selectors has been deprecated. Please use qbank_tagquestion/selectors instead.");return{actions:{save:"[data-action=\"save\"]",edittags:"[data-action=\"edittags\"]"},containers:{loadingIcon:"[data-region=\"overlay-icon-container\"]"}}});
//# sourceMappingURL=selectors.min.js.map
diff --git a/question/amd/build/selectors.min.js.map b/question/amd/build/selectors.min.js.map
index 8d3ec286f72ce..03a39ffd946de 100644
--- a/question/amd/build/selectors.min.js.map
+++ b/question/amd/build/selectors.min.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../src/selectors.js"],"names":["define","actions","save","edittags","containers","loadingIcon"],"mappings":"AAsBAA,OAAM,2BAAC,EAAD,CAAK,UAAW,CAClB,MAAO,CACHC,OAAO,CAAE,CACLC,IAAI,CAAE,wBADD,CAELC,QAAQ,CAAE,4BAFL,CADN,CAKHC,UAAU,CAAE,CACRC,WAAW,CAAE,0CADL,CALT,CASV,CAVK,CAAN","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 * The purpose of this module is to centralize selectors related to question.\n *\n * @module core_question/question_selectors\n * @copyright 2018 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([], function() {\n return {\n actions: {\n save: '[data-action=\"save\"]',\n edittags: '[data-action=\"edittags\"]',\n },\n containers: {\n loadingIcon: '[data-region=\"overlay-icon-container\"]',\n },\n };\n});\n"],"file":"selectors.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../src/selectors.js"],"names":["define","window","console","warn","actions","save","edittags","containers","loadingIcon"],"mappings":"AAwBAA,OAAM,2BAAC,EAAD,CAAK,UAAW,CAClBC,MAAM,CAACC,OAAP,CAAeC,IAAf,CAAoB,wGAApB,EACA,MAAO,CACHC,OAAO,CAAE,CACLC,IAAI,CAAE,wBADD,CAELC,QAAQ,CAAE,4BAFL,CADN,CAKHC,UAAU,CAAE,CACRC,WAAW,CAAE,0CADL,CALT,CASV,CAXK,CAAN","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 * The purpose of this module is to centralize selectors related to question.\n *\n * @deprecated since Moodle 4.0\n * @todo Final deprecation on Moodle 4.4 MDL-72438\n * @module core_question/question_selectors\n * @copyright 2018 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([], function() {\n window.console.warn('warn: The core_question/selectors has been deprecated. Please use qbank_tagquestion/selectors instead.');\n return {\n actions: {\n save: '[data-action=\"save\"]',\n edittags: '[data-action=\"edittags\"]',\n },\n containers: {\n loadingIcon: '[data-region=\"overlay-icon-container\"]',\n },\n };\n});\n"],"file":"selectors.min.js"}
\ No newline at end of file
diff --git a/question/amd/src/edit_tags.js b/question/amd/src/edit_tags.js
index 8c3f68483ec44..e952b8e6cf472 100644
--- a/question/amd/src/edit_tags.js
+++ b/question/amd/src/edit_tags.js
@@ -16,6 +16,8 @@
/**
* A javascript module to handle question tags editing.
*
+ * @deprecated since Moodle 4.0
+ * @todo Final deprecation on Moodle 4.4 MDL-72438
* @module core_question/edit_tags
* @copyright 2018 Simey Lameze
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -268,6 +270,8 @@ define([
return {
init: function(root) {
+ window.console.warn('warn: The core_question/repository has been deprecated.' +
+ 'Please use qbank_tagquestion/repository instead.');
root = $(root);
registerEventListeners(root);
}
diff --git a/question/amd/src/repository.js b/question/amd/src/repository.js
index bb054c7898167..40fafb528b8f6 100644
--- a/question/amd/src/repository.js
+++ b/question/amd/src/repository.js
@@ -16,6 +16,8 @@
/**
* A javascript module to handle question ajax actions.
*
+ * @deprecated since Moodle 4.0
+ * @todo Final deprecation on Moodle 4.4 MDL-72438
* @module core_question/repository
* @copyright 2017 Simey Lameze
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -30,6 +32,8 @@ define(['jquery', 'core/ajax'], function($, Ajax) {
* @return {promise}
*/
var submitTagCreateUpdateForm = function(questionId, contextId, formdata) {
+ window.console.warn('warn: The core_question/repository has been deprecated.' +
+ 'Please use qbank_tagquestion/repository instead.');
var request = {
methodname: 'core_question_submit_tags_form',
args: {
diff --git a/question/amd/src/selectors.js b/question/amd/src/selectors.js
index 1019cf36212c9..6a3fb874894b2 100644
--- a/question/amd/src/selectors.js
+++ b/question/amd/src/selectors.js
@@ -16,11 +16,14 @@
/**
* The purpose of this module is to centralize selectors related to question.
*
+ * @deprecated since Moodle 4.0
+ * @todo Final deprecation on Moodle 4.4 MDL-72438
* @module core_question/question_selectors
* @copyright 2018 Simey Lameze
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
+ window.console.warn('warn: The core_question/selectors has been deprecated. Please use qbank_tagquestion/selectors instead.');
return {
actions: {
save: '[data-action="save"]',
diff --git a/question/bank/tagquestion/amd/build/edit_tags.min.js b/question/bank/tagquestion/amd/build/edit_tags.min.js
new file mode 100644
index 0000000000000..7aad4512bc983
--- /dev/null
+++ b/question/bank/tagquestion/amd/build/edit_tags.min.js
@@ -0,0 +1,2 @@
+define ("qbank_tagquestion/edit_tags",["jquery","core/fragment","core/str","core/modal_events","core/modal_factory","core/notification","core/custom_interaction_events","qbank_tagquestion/repository","qbank_tagquestion/selectors"],function(a,b,c,d,e,f,g,h,i){var j=function(a){a.find(i.actions.save).prop("disabled",!1)},k=function(a){a.find(i.actions.save).prop("disabled",!0)},l=function(a){return a.getBody().find("form").serialize()},m=function(a){var b=a.find(i.containers.loadingIcon);b.removeClass("hidden")},n=function(a){var b=a.find(i.containers.loadingIcon);b.addClass("hidden")},o=function(a,b){a.getBody().attr("data-contextid",b)},p=function(a){return a.getBody().data("contextid")},q=function(a,b){a.getBody().attr("data-questionid",b)},r=function(a){return a.getBody().data("questionid")},s=function(h){var l=e.create({type:e.types.SAVE_CANCEL,large:!1},[h,i.actions.edittags]).then(function(a){c.get_string("questiontags","question").then(function(b){a.setTitle(b);return b}).fail(f.exception);a.getRoot().on(d.save,function(b){var c=a.getBody().find("form");c.submit();b.preventDefault()});a.getRoot().on("submit","form",function(b){t(a,h).then(function(){a.hide();location.reload()}).fail(f.exception);b.preventDefault();b.stopPropagation()});return a});h.on(g.events.activate,i.actions.edittags,function(c){var d=a(c.currentTarget),e=d.data("questionid"),g=!!d.data("cantag"),p=d.data("contextid");l.then(function(a){k(h);m(h);var c=b.loadFragment("qbank_tagquestion","tags_form",p,{id:e});a.setBody(c);c.then(function(){j(h)}).always(function(){n(h)}).fail(f.exception);if(g){a.getRoot().find(i.actions.save).show()}else{a.getRoot().find(i.actions.save).hide()}q(a,e);o(a,p);return a}).fail(f.exception);c.preventDefault()})},t=function(a,b){k(b);m(b);var c=l(a),d=r(a),e=p(a);return h.submitTagCreateUpdateForm(d,e,c).always(function(){n(b);j(b)}).fail(f.exception)};return{init:function init(b){b=a(b);s(b)}}});
+//# sourceMappingURL=edit_tags.min.js.map
diff --git a/question/bank/tagquestion/amd/build/edit_tags.min.js.map b/question/bank/tagquestion/amd/build/edit_tags.min.js.map
new file mode 100644
index 0000000000000..af31e293403de
--- /dev/null
+++ b/question/bank/tagquestion/amd/build/edit_tags.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../src/edit_tags.js"],"names":["define","$","Fragment","Str","ModalEvents","ModalFactory","Notification","CustomEvents","Repository","QuestionSelectors","enableSaveButton","root","find","actions","save","prop","disableSaveButton","getFormData","modal","getBody","serialize","startLoading","loadingIconContainer","containers","loadingIcon","removeClass","stopLoading","addClass","setContextId","contextId","attr","getContextId","data","setQuestionId","questionId","getQuestionId","registerEventListeners","modalPromise","create","type","types","SAVE_CANCEL","large","edittags","then","get_string","string","setTitle","fail","exception","getRoot","on","e","form","submit","preventDefault","hide","location","reload","stopPropagation","events","activate","currentTarget","canTag","tagsFragment","loadFragment","id","setBody","always","show","formData","submitTagCreateUpdateForm","init"],"mappings":"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","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 * A javascript module to handle question tags editing.\n *\n * @module qbank_tagquestion/edit_tags\n * @copyright 2018 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/fragment',\n 'core/str',\n 'core/modal_events',\n 'core/modal_factory',\n 'core/notification',\n 'core/custom_interaction_events',\n 'qbank_tagquestion/repository',\n 'qbank_tagquestion/selectors',\n ],\n function(\n $,\n Fragment,\n Str,\n ModalEvents,\n ModalFactory,\n Notification,\n CustomEvents,\n Repository,\n QuestionSelectors\n ) {\n\n /**\n * Enable the save button in the footer.\n *\n * @param {object} root The container element.\n * @method enableSaveButton\n */\n var enableSaveButton = function(root) {\n root.find(QuestionSelectors.actions.save).prop('disabled', false);\n };\n\n /**\n * Disable the save button in the footer.\n *\n * @param {object} root The container element.\n * @method disableSaveButton\n */\n var disableSaveButton = function(root) {\n root.find(QuestionSelectors.actions.save).prop('disabled', true);\n };\n\n /**\n * Get the serialised form data.\n *\n * @method getFormData\n * @param {object} modal The modal object.\n * @return {string} serialised form data\n */\n var getFormData = function(modal) {\n return modal.getBody().find('form').serialize();\n };\n\n /**\n * Set the element state to loading.\n *\n * @param {object} root The container element\n * @method startLoading\n */\n var startLoading = function(root) {\n var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);\n\n loadingIconContainer.removeClass('hidden');\n };\n\n /**\n * Remove the loading state from the element.\n *\n * @param {object} root The container element\n * @method stopLoading\n */\n var stopLoading = function(root) {\n var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);\n\n loadingIconContainer.addClass('hidden');\n };\n\n /**\n * Set the context Id data attribute on the modal.\n *\n * @param {Promise} modal The modal promise.\n * @param {int} contextId The context id.\n */\n var setContextId = function(modal, contextId) {\n modal.getBody().attr('data-contextid', contextId);\n };\n\n /**\n * Get the context Id data attribute value from the modal body.\n *\n * @param {Promise} modal The modal promise.\n * @return {int} The context id.\n */\n var getContextId = function(modal) {\n return modal.getBody().data('contextid');\n };\n\n /**\n * Set the question Id data attribute on the modal.\n *\n * @param {Promise} modal The modal promise.\n * @param {int} questionId The question Id.\n */\n var setQuestionId = function(modal, questionId) {\n modal.getBody().attr('data-questionid', questionId);\n };\n\n /**\n * Get the question Id data attribute value from the modal body.\n *\n * @param {Promise} modal The modal promise.\n * @return {int} The question Id.\n */\n var getQuestionId = function(modal) {\n return modal.getBody().data('questionid');\n };\n\n /**\n * Register event listeners for the module.\n *\n * @param {object} root The calendar root element\n */\n var registerEventListeners = function(root) {\n var modalPromise = ModalFactory.create(\n {\n type: ModalFactory.types.SAVE_CANCEL,\n large: false\n },\n [root, QuestionSelectors.actions.edittags]\n ).then(function(modal) {\n // All of this code only executes once, when the modal is\n // first created. This allows us to add any code that should\n // only be run once, such as adding event handlers to the modal.\n Str.get_string('questiontags', 'question')\n .then(function(string) {\n modal.setTitle(string);\n return string;\n })\n .fail(Notification.exception);\n\n modal.getRoot().on(ModalEvents.save, function(e) {\n var form = modal.getBody().find('form');\n form.submit();\n e.preventDefault();\n });\n\n modal.getRoot().on('submit', 'form', function(e) {\n save(modal, root).then(function() {\n modal.hide();\n location.reload();\n return;\n }).fail(Notification.exception);\n\n // Stop the form from actually submitting and prevent it's\n // propagation because we have already handled the event.\n e.preventDefault();\n e.stopPropagation();\n });\n\n return modal;\n });\n\n // We need to add an event handler to the tags link because there are\n // multiple links on the page and without adding a listener we don't know\n // which one the user clicked on the show the modal.\n root.on(CustomEvents.events.activate, QuestionSelectors.actions.edittags, function(e) {\n var currentTarget = $(e.currentTarget);\n\n var questionId = currentTarget.data('questionid'),\n canTag = !!currentTarget.data('cantag'),\n contextId = currentTarget.data('contextid');\n\n // This code gets called each time the user clicks the tag link\n // so we can use it to reload the contents of the tag modal.\n modalPromise.then(function(modal) {\n // Display spinner and disable save button.\n disableSaveButton(root);\n startLoading(root);\n\n var args = {\n id: questionId\n };\n\n var tagsFragment = Fragment.loadFragment('qbank_tagquestion', 'tags_form', contextId, args);\n modal.setBody(tagsFragment);\n\n tagsFragment.then(function() {\n enableSaveButton(root);\n return;\n })\n .always(function() {\n // Always hide the loading spinner when the request\n // has completed.\n stopLoading(root);\n return;\n })\n .fail(Notification.exception);\n\n // Show or hide the save button depending on whether the user\n // has the capability to edit the tags.\n if (canTag) {\n modal.getRoot().find(QuestionSelectors.actions.save).show();\n } else {\n modal.getRoot().find(QuestionSelectors.actions.save).hide();\n }\n\n setQuestionId(modal, questionId);\n setContextId(modal, contextId);\n\n return modal;\n }).fail(Notification.exception);\n\n e.preventDefault();\n });\n };\n\n /**\n * Send the form data to the server to save question tags.\n *\n * @method save\n * @param {object} modal The modal object.\n * @param {object} root The container element.\n * @return {object} A promise\n */\n var save = function(modal, root) {\n // Display spinner and disable save button.\n disableSaveButton(root);\n startLoading(root);\n\n var formData = getFormData(modal);\n var questionId = getQuestionId(modal);\n var contextId = getContextId(modal);\n\n // Send the form data to the server for processing.\n return Repository.submitTagCreateUpdateForm(questionId, contextId, formData)\n .always(function() {\n // Regardless of success or error we should always stop\n // the loading icon and re-enable the buttons.\n stopLoading(root);\n enableSaveButton(root);\n return;\n })\n .fail(Notification.exception);\n };\n\n return {\n init: function(root) {\n root = $(root);\n registerEventListeners(root);\n }\n };\n});\n"],"file":"edit_tags.min.js"}
\ No newline at end of file
diff --git a/question/bank/tagquestion/amd/build/repository.min.js b/question/bank/tagquestion/amd/build/repository.min.js
new file mode 100644
index 0000000000000..8f944664b7113
--- /dev/null
+++ b/question/bank/tagquestion/amd/build/repository.min.js
@@ -0,0 +1,2 @@
+define ("qbank_tagquestion/repository",["jquery","core/ajax"],function(a,b){return{submitTagCreateUpdateForm:function submitTagCreateUpdateForm(a,c,d){return b.call([{methodname:"qbank_tagquestion_submit_tags_form",args:{questionid:a,contextid:c,formdata:d}}])[0]}}});
+//# sourceMappingURL=repository.min.js.map
diff --git a/question/bank/tagquestion/amd/build/repository.min.js.map b/question/bank/tagquestion/amd/build/repository.min.js.map
new file mode 100644
index 0000000000000..26cff3e3f035d
--- /dev/null
+++ b/question/bank/tagquestion/amd/build/repository.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../src/repository.js"],"names":["define","$","Ajax","submitTagCreateUpdateForm","questionId","contextId","formdata","call","methodname","args","questionid","contextid"],"mappings":"AAwBAA,OAAM,gCAAC,CAAC,QAAD,CAAW,WAAX,CAAD,CAA0B,SAASC,CAAT,CAAYC,CAAZ,CAAkB,CAwB9C,MAAO,CACHC,yBAAyB,CAdG,QAA5BA,CAAAA,yBAA4B,CAASC,CAAT,CAAqBC,CAArB,CAAgCC,CAAhC,CAA0C,CAUtE,MAAOJ,CAAAA,CAAI,CAACK,IAAL,CAAU,CATH,CACVC,UAAU,CAAE,oCADF,CAEVC,IAAI,CAAE,CACFC,UAAU,CAAEN,CADV,CAEFO,SAAS,CAAEN,CAFT,CAGFC,QAAQ,CAAEA,CAHR,CAFI,CASG,CAAV,EAAqB,CAArB,CACV,CAEM,CAGV,CA3BK,CAAN","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 * A javascript module to handle question ajax actions.\n *\n * @module qbank_tagquestion/repository\n * @class repository\n * @package qbank_tagquestion\n * @copyright 2017 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax'], function($, Ajax) {\n\n /**\n * Submit the form data for the question tags form.\n *\n * @method submitTagCreateUpdateForm\n * @param {int} questionId\n * @param {int} contextId\n * @param {string} formdata The URL encoded values from the form\n * @return {promise}\n */\n var submitTagCreateUpdateForm = function(questionId, contextId, formdata) {\n var request = {\n methodname: 'qbank_tagquestion_submit_tags_form',\n args: {\n questionid: questionId,\n contextid: contextId,\n formdata: formdata\n }\n };\n\n return Ajax.call([request])[0];\n };\n\n return {\n submitTagCreateUpdateForm: submitTagCreateUpdateForm\n };\n});\n"],"file":"repository.min.js"}
\ No newline at end of file
diff --git a/question/bank/tagquestion/amd/build/selectors.min.js b/question/bank/tagquestion/amd/build/selectors.min.js
new file mode 100644
index 0000000000000..9eaae0436c641
--- /dev/null
+++ b/question/bank/tagquestion/amd/build/selectors.min.js
@@ -0,0 +1,2 @@
+define ("qbank_tagquestion/selectors",[],function(){return{actions:{save:"[data-action=\"save\"]",edittags:"[data-action=\"edittags\"]"},containers:{loadingIcon:"[data-region=\"overlay-icon-container\"]"}}});
+//# sourceMappingURL=selectors.min.js.map
diff --git a/question/bank/tagquestion/amd/build/selectors.min.js.map b/question/bank/tagquestion/amd/build/selectors.min.js.map
new file mode 100644
index 0000000000000..b67431b5d5326
--- /dev/null
+++ b/question/bank/tagquestion/amd/build/selectors.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../src/selectors.js"],"names":["define","actions","save","edittags","containers","loadingIcon"],"mappings":"AAuBAA,OAAM,+BAAC,EAAD,CAAK,UAAW,CAClB,MAAO,CACHC,OAAO,CAAE,CACLC,IAAI,CAAE,wBADD,CAELC,QAAQ,CAAE,4BAFL,CADN,CAKHC,UAAU,CAAE,CACRC,WAAW,CAAE,0CADL,CALT,CASV,CAVK,CAAN","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 * The purpose of this module is to centralize selectors related to question.\n *\n * @module qbank_tagquestion/question_selectors\n * @package qbank_tagquestion\n * @copyright 2018 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([], function() {\n return {\n actions: {\n save: '[data-action=\"save\"]',\n edittags: '[data-action=\"edittags\"]',\n },\n containers: {\n loadingIcon: '[data-region=\"overlay-icon-container\"]',\n },\n };\n});\n"],"file":"selectors.min.js"}
\ No newline at end of file
diff --git a/question/bank/tagquestion/amd/src/edit_tags.js b/question/bank/tagquestion/amd/src/edit_tags.js
new file mode 100644
index 0000000000000..50bdc1dcd357c
--- /dev/null
+++ b/question/bank/tagquestion/amd/src/edit_tags.js
@@ -0,0 +1,275 @@
+// 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 .
+
+/**
+ * A javascript module to handle question tags editing.
+ *
+ * @module qbank_tagquestion/edit_tags
+ * @copyright 2018 Simey Lameze
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define([
+ 'jquery',
+ 'core/fragment',
+ 'core/str',
+ 'core/modal_events',
+ 'core/modal_factory',
+ 'core/notification',
+ 'core/custom_interaction_events',
+ 'qbank_tagquestion/repository',
+ 'qbank_tagquestion/selectors',
+ ],
+ function(
+ $,
+ Fragment,
+ Str,
+ ModalEvents,
+ ModalFactory,
+ Notification,
+ CustomEvents,
+ Repository,
+ QuestionSelectors
+ ) {
+
+ /**
+ * Enable the save button in the footer.
+ *
+ * @param {object} root The container element.
+ * @method enableSaveButton
+ */
+ var enableSaveButton = function(root) {
+ root.find(QuestionSelectors.actions.save).prop('disabled', false);
+ };
+
+ /**
+ * Disable the save button in the footer.
+ *
+ * @param {object} root The container element.
+ * @method disableSaveButton
+ */
+ var disableSaveButton = function(root) {
+ root.find(QuestionSelectors.actions.save).prop('disabled', true);
+ };
+
+ /**
+ * Get the serialised form data.
+ *
+ * @method getFormData
+ * @param {object} modal The modal object.
+ * @return {string} serialised form data
+ */
+ var getFormData = function(modal) {
+ return modal.getBody().find('form').serialize();
+ };
+
+ /**
+ * Set the element state to loading.
+ *
+ * @param {object} root The container element
+ * @method startLoading
+ */
+ var startLoading = function(root) {
+ var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);
+
+ loadingIconContainer.removeClass('hidden');
+ };
+
+ /**
+ * Remove the loading state from the element.
+ *
+ * @param {object} root The container element
+ * @method stopLoading
+ */
+ var stopLoading = function(root) {
+ var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);
+
+ loadingIconContainer.addClass('hidden');
+ };
+
+ /**
+ * Set the context Id data attribute on the modal.
+ *
+ * @param {Promise} modal The modal promise.
+ * @param {int} contextId The context id.
+ */
+ var setContextId = function(modal, contextId) {
+ modal.getBody().attr('data-contextid', contextId);
+ };
+
+ /**
+ * Get the context Id data attribute value from the modal body.
+ *
+ * @param {Promise} modal The modal promise.
+ * @return {int} The context id.
+ */
+ var getContextId = function(modal) {
+ return modal.getBody().data('contextid');
+ };
+
+ /**
+ * Set the question Id data attribute on the modal.
+ *
+ * @param {Promise} modal The modal promise.
+ * @param {int} questionId The question Id.
+ */
+ var setQuestionId = function(modal, questionId) {
+ modal.getBody().attr('data-questionid', questionId);
+ };
+
+ /**
+ * Get the question Id data attribute value from the modal body.
+ *
+ * @param {Promise} modal The modal promise.
+ * @return {int} The question Id.
+ */
+ var getQuestionId = function(modal) {
+ return modal.getBody().data('questionid');
+ };
+
+ /**
+ * Register event listeners for the module.
+ *
+ * @param {object} root The calendar root element
+ */
+ var registerEventListeners = function(root) {
+ var modalPromise = ModalFactory.create(
+ {
+ type: ModalFactory.types.SAVE_CANCEL,
+ large: false
+ },
+ [root, QuestionSelectors.actions.edittags]
+ ).then(function(modal) {
+ // All of this code only executes once, when the modal is
+ // first created. This allows us to add any code that should
+ // only be run once, such as adding event handlers to the modal.
+ Str.get_string('questiontags', 'question')
+ .then(function(string) {
+ modal.setTitle(string);
+ return string;
+ })
+ .fail(Notification.exception);
+
+ modal.getRoot().on(ModalEvents.save, function(e) {
+ var form = modal.getBody().find('form');
+ form.submit();
+ e.preventDefault();
+ });
+
+ modal.getRoot().on('submit', 'form', function(e) {
+ save(modal, root).then(function() {
+ modal.hide();
+ location.reload();
+ return;
+ }).fail(Notification.exception);
+
+ // Stop the form from actually submitting and prevent it's
+ // propagation because we have already handled the event.
+ e.preventDefault();
+ e.stopPropagation();
+ });
+
+ return modal;
+ });
+
+ // We need to add an event handler to the tags link because there are
+ // multiple links on the page and without adding a listener we don't know
+ // which one the user clicked on the show the modal.
+ root.on(CustomEvents.events.activate, QuestionSelectors.actions.edittags, function(e) {
+ var currentTarget = $(e.currentTarget);
+
+ var questionId = currentTarget.data('questionid'),
+ canTag = !!currentTarget.data('cantag'),
+ contextId = currentTarget.data('contextid');
+
+ // This code gets called each time the user clicks the tag link
+ // so we can use it to reload the contents of the tag modal.
+ modalPromise.then(function(modal) {
+ // Display spinner and disable save button.
+ disableSaveButton(root);
+ startLoading(root);
+
+ var args = {
+ id: questionId
+ };
+
+ var tagsFragment = Fragment.loadFragment('qbank_tagquestion', 'tags_form', contextId, args);
+ modal.setBody(tagsFragment);
+
+ tagsFragment.then(function() {
+ enableSaveButton(root);
+ return;
+ })
+ .always(function() {
+ // Always hide the loading spinner when the request
+ // has completed.
+ stopLoading(root);
+ return;
+ })
+ .fail(Notification.exception);
+
+ // Show or hide the save button depending on whether the user
+ // has the capability to edit the tags.
+ if (canTag) {
+ modal.getRoot().find(QuestionSelectors.actions.save).show();
+ } else {
+ modal.getRoot().find(QuestionSelectors.actions.save).hide();
+ }
+
+ setQuestionId(modal, questionId);
+ setContextId(modal, contextId);
+
+ return modal;
+ }).fail(Notification.exception);
+
+ e.preventDefault();
+ });
+ };
+
+ /**
+ * Send the form data to the server to save question tags.
+ *
+ * @method save
+ * @param {object} modal The modal object.
+ * @param {object} root The container element.
+ * @return {object} A promise
+ */
+ var save = function(modal, root) {
+ // Display spinner and disable save button.
+ disableSaveButton(root);
+ startLoading(root);
+
+ var formData = getFormData(modal);
+ var questionId = getQuestionId(modal);
+ var contextId = getContextId(modal);
+
+ // Send the form data to the server for processing.
+ return Repository.submitTagCreateUpdateForm(questionId, contextId, formData)
+ .always(function() {
+ // Regardless of success or error we should always stop
+ // the loading icon and re-enable the buttons.
+ stopLoading(root);
+ enableSaveButton(root);
+ return;
+ })
+ .fail(Notification.exception);
+ };
+
+ return {
+ init: function(root) {
+ root = $(root);
+ registerEventListeners(root);
+ }
+ };
+});
diff --git a/question/bank/tagquestion/amd/src/repository.js b/question/bank/tagquestion/amd/src/repository.js
new file mode 100644
index 0000000000000..9141813353e3f
--- /dev/null
+++ b/question/bank/tagquestion/amd/src/repository.js
@@ -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 .
+
+/**
+ * A javascript module to handle question ajax actions.
+ *
+ * @module qbank_tagquestion/repository
+ * @class repository
+ * @package qbank_tagquestion
+ * @copyright 2017 Simey Lameze
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/ajax'], function($, Ajax) {
+
+ /**
+ * Submit the form data for the question tags form.
+ *
+ * @method submitTagCreateUpdateForm
+ * @param {int} questionId
+ * @param {int} contextId
+ * @param {string} formdata The URL encoded values from the form
+ * @return {promise}
+ */
+ var submitTagCreateUpdateForm = function(questionId, contextId, formdata) {
+ var request = {
+ methodname: 'qbank_tagquestion_submit_tags_form',
+ args: {
+ questionid: questionId,
+ contextid: contextId,
+ formdata: formdata
+ }
+ };
+
+ return Ajax.call([request])[0];
+ };
+
+ return {
+ submitTagCreateUpdateForm: submitTagCreateUpdateForm
+ };
+});
diff --git a/question/bank/tagquestion/amd/src/selectors.js b/question/bank/tagquestion/amd/src/selectors.js
new file mode 100644
index 0000000000000..a3de37f0b2103
--- /dev/null
+++ b/question/bank/tagquestion/amd/src/selectors.js
@@ -0,0 +1,34 @@
+// 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 .
+
+/**
+ * The purpose of this module is to centralize selectors related to question.
+ *
+ * @module qbank_tagquestion/question_selectors
+ * @package qbank_tagquestion
+ * @copyright 2018 Simey Lameze
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define([], function() {
+ return {
+ actions: {
+ save: '[data-action="save"]',
+ edittags: '[data-action="edittags"]',
+ },
+ containers: {
+ loadingIcon: '[data-region="overlay-icon-container"]',
+ },
+ };
+});
diff --git a/question/bank/tagquestion/classes/external/submit_tags.php b/question/bank/tagquestion/classes/external/submit_tags.php
new file mode 100644
index 0000000000000..9d6b36fb3abf5
--- /dev/null
+++ b/question/bank/tagquestion/classes/external/submit_tags.php
@@ -0,0 +1,135 @@
+.
+
+namespace qbank_tagquestion\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/externallib.php');
+require_once($CFG->dirroot . '/question/engine/lib.php');
+require_once($CFG->dirroot . '/question/engine/datalib.php');
+require_once($CFG->libdir . '/questionlib.php');
+
+use core_tag_tag;
+use external_api;
+use external_function_parameters;
+use external_single_structure;
+use external_value;
+use qbank_tagquestion\form\tags_form;
+
+/**
+ * External qbank_tagquestion API.
+ *
+ * @package qbank_tagquestion
+ * @copyright 2016 Pau Ferrer
+ * @author 2021 Safat Shahin
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class submit_tags extends external_api {
+ /**
+ * Returns description of method parameters.
+ *
+ * @return external_function_parameters.
+ */
+ public static function execute_parameters() {
+ return new external_function_parameters([
+ 'questionid' => new external_value(PARAM_INT, 'The question id'),
+ 'contextid' => new external_value(PARAM_INT, 'The editing context id'),
+ 'formdata' => new external_value(PARAM_RAW, 'The data from the tag form'),
+ ]);
+ }
+
+ /**
+ * Handles the tags form submission.
+ *
+ * @param int $questionid The question id.
+ * @param int $contextid The editing context id.
+ * @param string $formdata The question tag form data in a URI encoded param string
+ * @return array The created or modified question tag
+ */
+ public static function execute($questionid, $contextid, $formdata) {
+ global $DB, $CFG;
+
+ $data = [];
+ $result = ['status' => false];
+
+ // Parameter validation.
+ $params = self::validate_parameters(self::execute_parameters(), [
+ 'questionid' => $questionid,
+ 'contextid' => $contextid,
+ 'formdata' => $formdata
+ ]);
+
+ $editingcontext = \context::instance_by_id($params['contextid']);
+ self::validate_context($editingcontext);
+ parse_str($params['formdata'], $data);
+
+ if (!$question = $DB->get_record_sql('
+ SELECT q.*, qc.contextid
+ FROM {question} q
+ JOIN {question_categories} qc ON qc.id = q.category
+ WHERE q.id = ?', [$params['questionid']])) {
+ throw new \moodle_exception('questiondoesnotexist', 'question');
+ }
+
+ $cantag = question_has_capability_on($question, 'tag');
+ $questioncontext = \context::instance_by_id($question->contextid);
+ $contexts = new \question_edit_contexts($editingcontext);
+
+ $formoptions = [
+ 'editingcontext' => $editingcontext,
+ 'questioncontext' => $questioncontext,
+ 'contexts' => $contexts->all()
+ ];
+
+ $mform = new tags_form(null, $formoptions, 'post', '', null, $cantag, $data);
+
+ if ($validateddata = $mform->get_data()) {
+ if ($cantag) {
+ if (isset($validateddata->tags)) {
+ // Due to a mform bug, if there's no tags set on the tag element, it submits the name as the value.
+ // The only way to discover is checking if the tag element is an array.
+ $tags = is_array($validateddata->tags) ? $validateddata->tags : [];
+
+ core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id,
+ $questioncontext, $tags);
+
+ $result['status'] = true;
+ }
+
+ if (isset($validateddata->coursetags)) {
+ $coursetags = is_array($validateddata->coursetags) ? $validateddata->coursetags : [];
+ core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id,
+ $editingcontext->get_course_context(false), $coursetags);
+
+ $result['status'] = true;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns description of method result value.
+ */
+ public static function execute_returns() {
+ return new external_single_structure([
+ 'status' => new external_value(PARAM_BOOL, 'status: true if success')
+ ]);
+ }
+
+}
diff --git a/question/bank/tagquestion/classes/form/tags_form.php b/question/bank/tagquestion/classes/form/tags_form.php
new file mode 100644
index 0000000000000..7269b51935d20
--- /dev/null
+++ b/question/bank/tagquestion/classes/form/tags_form.php
@@ -0,0 +1,88 @@
+.
+
+namespace qbank_tagquestion\form;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/lib/formslib.php');
+require_once($CFG->dirroot . '/lib/questionlib.php');
+
+/**
+ * The mform class for manage question tags.
+ *
+ * @package qbank_tagquestion
+ * @copyright 2018 Simey Lameze
+ * @author 2021 Safat Shahin
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tags_form extends \moodleform {
+
+ public function definition() {
+ $mform = $this->_form;
+ $customdata = $this->_customdata;
+
+ $mform->disable_form_change_checker();
+
+ $mform->addElement('hidden', 'id');
+ $mform->setType('id', PARAM_INT);
+
+ $mform->addElement('hidden', 'categoryid');
+ $mform->setType('categoryid', PARAM_INT);
+
+ $mform->addElement('hidden', 'contextid');
+ $mform->setType('contextid', PARAM_INT);
+
+ $mform->addElement('static', 'questionname', get_string('questionname', 'question'));
+ $mform->addElement('static', 'questioncategory', get_string('categorycurrent', 'question'));
+ $mform->addElement('static', 'context', '');
+
+ if (\core_tag_tag::is_enabled('core_question', 'question')) {
+ $tags = \core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $customdata['contexts']);
+ $tagstrings = [];
+ foreach ($tags as $tag) {
+ $tagstrings[$tag->name] = $tag->name;
+ }
+
+ $options = [
+ 'tags' => true,
+ 'multiple' => true,
+ 'noselectionstring' => get_string('anytags', 'quiz'),
+ ];
+ $mform->addElement('autocomplete', 'tags', get_string('tags'), $tagstrings, $options);
+
+ // Is the question category in a course context?
+ $qcontext = $customdata['questioncontext'];
+ $qcoursecontext = $qcontext->get_course_context(false);
+ $iscourseoractivityquestion = !empty($qcoursecontext);
+ // Is the current context we're editing in a course context?
+ $editingcontext = $customdata['editingcontext'];
+ $editingcoursecontext = $editingcontext->get_course_context(false);
+ $iseditingcontextcourseoractivity = !empty($editingcoursecontext);
+
+ if ($iseditingcontextcourseoractivity && !$iscourseoractivityquestion) {
+ // If the question is being edited in a course or activity context
+ // and the question isn't a course or activity level question then
+ // allow course tags to be added to the course.
+ $coursetagheader = get_string('questionformtagheader', 'core_question',
+ $editingcoursecontext->get_context_name(true));
+ $mform->addElement('autocomplete', 'coursetags', $coursetagheader, $tagstrings, $options);
+
+ }
+ }
+ }
+
+}
diff --git a/question/bank/tagquestion/classes/plugin_feature.php b/question/bank/tagquestion/classes/plugin_feature.php
new file mode 100644
index 0000000000000..792dc78021716
--- /dev/null
+++ b/question/bank/tagquestion/classes/plugin_feature.php
@@ -0,0 +1,36 @@
+.
+
+namespace qbank_tagquestion;
+
+use core_question\local\bank\plugin_features_base;
+
+/**
+ * Class columns is the entrypoint for the columns.
+ *
+ * @package qbank_tagquestion
+ * @copyright 2021 Catalyst IT Australia Pty Ltd
+ * @author Safat Shahin
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class plugin_feature extends plugin_features_base{
+
+ public function get_question_columns($qbank): array {
+ return [
+ new tags_action_column($qbank),
+ ];
+ }
+}
diff --git a/question/bank/tagquestion/classes/privacy/provider.php b/question/bank/tagquestion/classes/privacy/provider.php
new file mode 100644
index 0000000000000..edb86c5d89537
--- /dev/null
+++ b/question/bank/tagquestion/classes/privacy/provider.php
@@ -0,0 +1,32 @@
+.
+
+namespace qbank_tagquestion\privacy;
+
+/**
+ * Privacy Subsystem for qbank_tagquestion implementing null_provider.
+ *
+ * @package qbank_tagquestion
+ * @copyright 2021 Catalyst IT Australia Pty Ltd
+ * @author Safat Shahin
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+ public static function get_reason(): string {
+ return 'privacy:metadata';
+ }
+}
diff --git a/question/bank/tagquestion/classes/tags_action_column.php b/question/bank/tagquestion/classes/tags_action_column.php
new file mode 100644
index 0000000000000..4a4edb05a4cc1
--- /dev/null
+++ b/question/bank/tagquestion/classes/tags_action_column.php
@@ -0,0 +1,98 @@
+.
+
+namespace qbank_tagquestion;
+
+use core_question\local\bank\action_column_base;
+use core_question\local\bank\menuable_action;
+
+/**
+ * Action to add and remove tags to questions.
+ *
+ * @package qbank_tagquestion
+ * @copyright 2018 Simey Lameze
+ * @author 2021 Safat Shahin
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tags_action_column extends action_column_base implements menuable_action {
+
+ /**
+ * @var string store this lang string for performance.
+ */
+ protected $managetags;
+
+ /**
+ * @var bool tags enabled or not from config.
+ */
+ protected $tagsenabled = true;
+
+ public function init(): void {
+ parent::init();
+ $this->check_tags_status();
+ if ($this->tagsenabled) {
+ global $PAGE;
+ $PAGE->requires->js_call_amd('qbank_tagquestion/edit_tags', 'init', ['#questionscontainer']);
+ }
+ $this->managetags = get_string('managetags', 'tag');
+ }
+
+ protected function check_tags_status(): void {
+ global $CFG;
+ if (!$CFG->usetags) {
+ $this->tagsenabled = false;
+ }
+ }
+
+ public function get_name(): string {
+ return 'tagsaction';
+ }
+
+ protected function display_content($question, $rowclasses): void {
+ global $OUTPUT;
+
+ if (\core_tag_tag::is_enabled('core_question', 'question') &&
+ question_has_capability_on($question, 'view') && $this->tagsenabled) {
+
+ [$url, $attributes] = $this->get_link_url_and_attributes($question);
+ echo \html_writer::link($url, $OUTPUT->pix_icon('t/tags',
+ $this->managetags), $attributes);
+ }
+ }
+
+ protected function get_link_url_and_attributes($question): array {
+ $url = new \moodle_url($this->qbank->returnurl);
+
+ $attributes = [
+ 'data-action' => 'edittags',
+ 'data-cantag' => question_has_capability_on($question, 'tag'),
+ 'data-contextid' => $this->qbank->get_most_specific_context()->id,
+ 'data-questionid' => $question->id
+ ];
+
+ return [$url, $attributes];
+ }
+
+ public function get_action_menu_link(\stdClass $question): ?\action_menu_link {
+ if (!\core_tag_tag::is_enabled('core_question', 'question') ||
+ !question_has_capability_on($question, 'view') || !$this->tagsenabled) {
+ return null;
+ }
+
+ [$url, $attributes] = $this->get_link_url_and_attributes($question);
+ return new \action_menu_link_secondary($url, new \pix_icon('t/tags', ''),
+ $this->managetags, $attributes);
+ }
+}
diff --git a/question/bank/tagquestion/db/services.php b/question/bank/tagquestion/db/services.php
new file mode 100644
index 0000000000000..61372ccb47fb5
--- /dev/null
+++ b/question/bank/tagquestion/db/services.php
@@ -0,0 +1,35 @@
+.
+
+/**
+ * External services definition for qbank_tagquestion.
+ *
+ * @package qbank_tagquestion
+ * @copyright 2021 Catalyst IT Australia Pty Ltd
+ * @author Safat Shahin
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$functions = [
+ 'qbank_tagquestion_submit_tags_form' => [
+ 'classname' => 'qbank_tagquestion\external\submit_tags',
+ 'description' => 'Update the question tags.',
+ 'type' => 'write',
+ 'ajax' => true,
+ ],
+];
diff --git a/question/bank/tagquestion/lang/en/qbank_tagquestion.php b/question/bank/tagquestion/lang/en/qbank_tagquestion.php
new file mode 100644
index 0000000000000..7042f5c8d99d9
--- /dev/null
+++ b/question/bank/tagquestion/lang/en/qbank_tagquestion.php
@@ -0,0 +1,27 @@
+.
+
+/**
+ * Strings for component qbank_tagquestion, language 'en'.
+ *
+ * @package qbank_tagquestion
+ * @copyright 2021 Catalyst IT Australia Pty Ltd
+ * @author Safat Shahin
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['pluginname'] = 'Tag question';
+$string['privacy:metadata'] = 'Tag question plugin does not store any user data.';
diff --git a/question/bank/tagquestion/lib.php b/question/bank/tagquestion/lib.php
new file mode 100644
index 0000000000000..936490010e4c9
--- /dev/null
+++ b/question/bank/tagquestion/lib.php
@@ -0,0 +1,92 @@
+.
+
+/**
+ * Question related functions.
+ *
+ * This file was created just because Fragment API expects callbacks to be defined on lib.php.
+ *
+ * @package qbank_tagquestion
+ * @copyright 2018 Simey Lameze
+ * @author 2021 Safat Shahin
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use qbank_tagquestion\form\tags_form;
+
+/**
+ * Question tags fragment callback.
+ *
+ * @param array $args Arguments to the form.
+ * @return null|string The rendered form.
+ */
+function qbank_tagquestion_output_fragment_tags_form($args) {
+
+ if (!empty($args['id'])) {
+ global $CFG, $DB;
+ require_once($CFG->libdir . '/questionlib.php');
+ $id = clean_param($args['id'], PARAM_INT);
+ $editingcontext = $args['context'];
+
+ // Load the question and some related information.
+ $question = $DB->get_record('question', ['id' => $id]);
+
+ if ($coursecontext = $editingcontext->get_course_context(false)) {
+ $course = $DB->get_record('course', ['id' => $coursecontext->instanceid]);
+ $filtercourses = [$course];
+ } else {
+ $filtercourses = null;
+ }
+
+ $category = $DB->get_record('question_categories', ['id' => $question->category]);
+ $questioncontext = \context::instance_by_id($category->contextid);
+ $contexts = new \question_edit_contexts($editingcontext);
+
+ // Load the question tags and filter the course tags by the current course.
+ if (core_tag_tag::is_enabled('core_question', 'question')) {
+ $tagobjectsbyquestion = core_tag_tag::get_items_tags('core_question', 'question', [$question->id]);
+ if (!empty($tagobjectsbyquestion[$question->id])) {
+ $tagobjects = $tagobjectsbyquestion[$question->id];
+ $sortedtagobjects = question_sort_tags($tagobjects,
+ context::instance_by_id($category->contextid), $filtercourses);
+ }
+ }
+ $formoptions = [
+ 'editingcontext' => $editingcontext,
+ 'questioncontext' => $questioncontext,
+ 'contexts' => $contexts->all()
+ ];
+ $data = [
+ 'id' => $question->id,
+ 'questioncategory' => $category->name,
+ 'questionname' => $question->name,
+ 'categoryid' => $category->id,
+ 'contextid' => $category->contextid,
+ 'context' => $questioncontext->get_context_name(),
+ 'tags' => $sortedtagobjects->tags ?? [],
+ 'coursetags' => $sortedtagobjects->coursetags ?? [],
+ ];
+
+ $cantag = question_has_capability_on($question, 'tag');
+ $mform = new tags_form(null, $formoptions, 'post', '', null, $cantag, $data);
+ $mform->set_data($data);
+
+ return $mform->render();
+ }
+
+}
diff --git a/question/bank/tagquestion/tests/behat/tag_question_action.feature b/question/bank/tagquestion/tests/behat/tag_question_action.feature
new file mode 100644
index 0000000000000..693017c251a10
--- /dev/null
+++ b/question/bank/tagquestion/tests/behat/tag_question_action.feature
@@ -0,0 +1,49 @@
+@qbank @qbank_tagquestion
+Feature: Use the qbank plugin manager page for tagquestion
+ In order to check the plugin behaviour with enable and disable
+
+ Background:
+ Given the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "activities" exist:
+ | activity | name | course | idnumber |
+ | quiz | Test quiz | C1 | quiz1 |
+ And the following "question categories" exist:
+ | contextlevel | reference | name |
+ | Course | C1 | Test questions |
+ And the following "questions" exist:
+ | questioncategory | qtype | name | questiontext |
+ | Test questions | truefalse | First question | Answer the first question |
+
+ Scenario: Enable/disable tagquestion column from the base view
+ Given I log in as "admin"
+ When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
+ And I should see "Tag question"
+ And I click on "Disable" "link" in the "Tag question" "table_row"
+ And I am on the "Test quiz" "quiz activity" page
+ And I navigate to "Question bank > Questions" in current page administration
+ And I click on "#action-menu-toggle-2" "css_element" in the "First question" "table_row"
+ Then I should not see "Manage tags"
+ And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
+ And I click on "Enable" "link" in the "Tag question" "table_row"
+ And I am on the "Test quiz" "quiz activity" page
+ And I navigate to "Question bank > Questions" in current page administration
+ And I click on "#action-menu-toggle-2" "css_element" in the "First question" "table_row"
+ And I should see "Manage tags"
+
+ Scenario: Enable/disable tagquestion section from question edit form
+ Given I log in as "admin"
+ When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
+ And I should see "Tag question"
+ And I click on "Disable" "link" in the "Tag question" "table_row"
+ And I am on the "Test quiz" "quiz activity" page
+ And I navigate to "Question bank > Questions" in current page administration
+ And I choose "Edit question" action for "First question" in the question bank
+ Then I should not see "Tags"
+ And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
+ And I click on "Enable" "link" in the "Tag question" "table_row"
+ And I am on the "Test quiz" "quiz activity" page
+ And I navigate to "Question bank > Questions" in current page administration
+ And I choose "Edit question" action for "First question" in the question bank
+ And I should see "Tags"
diff --git a/question/bank/tagquestion/tests/external/submit_tags_test.php b/question/bank/tagquestion/tests/external/submit_tags_test.php
new file mode 100644
index 0000000000000..f4d5602abe0c1
--- /dev/null
+++ b/question/bank/tagquestion/tests/external/submit_tags_test.php
@@ -0,0 +1,420 @@
+.
+
+namespace qbank_tagquestion\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+/**
+ * Question external functions tests.
+ *
+ * @package qbank_tagquestion
+ * @copyright 2016 Pau Ferrer
+ * @author 2021 Safat Shahin
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class submit_tags_test extends \externallib_advanced_testcase {
+
+ /**
+ * Set up for every test
+ */
+ public function setUp(): void {
+ global $DB;
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Setup test data.
+ $this->course = $this->getDataGenerator()->create_course();
+
+ // Create users.
+ $this->student = self::getDataGenerator()->create_user();
+
+ // Users enrolments.
+ $this->studentrole = $DB->get_record('role', ['shortname' => 'student']);
+ $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
+ }
+
+ /**
+ * submit_tags_form should throw an exception when the question id doesn't match
+ * a question.
+ */
+ public function test_submit_tags_form_incorrect_question_id() {
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
+ $questioncontext = \context::instance_by_id($qcat->contextid);
+ $editingcontext = $questioncontext;
+ $question = $questions[0];
+ // Generate an id for a question that doesn't exist.
+ $missingquestionid = $questions[1]->id * 2;
+ $question->id = $missingquestionid;
+ $formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []);
+
+ // We should receive an exception if the question doesn't exist.
+ $this->expectException('moodle_exception');
+ submit_tags::execute($missingquestionid, $editingcontext->id, $formdata);
+ }
+
+ /**
+ * submit_tags_form should throw an exception when the context id doesn't match
+ * a context.
+ */
+ public function test_submit_tags_form_incorrect_context_id() {
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
+ $questioncontext = \context::instance_by_id($qcat->contextid);
+ $editingcontext = $questioncontext;
+ $question = $questions[0];
+ // Generate an id for a context that doesn't exist.
+ $missingcontextid = $editingcontext->id * 200;
+ $formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []);
+
+ // We should receive an exception if the question doesn't exist.
+ $this->expectException('moodle_exception');
+ submit_tags::execute($question->id, $missingcontextid, $formdata);
+ }
+
+ /**
+ * submit_tags_form should return false when tags are disabled.
+ */
+ public function test_submit_tags_form_tags_disabled() {
+ global $CFG;
+
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
+ $questioncontext = \context::instance_by_id($qcat->contextid);
+ $editingcontext = $questioncontext;
+ $question = $questions[0];
+ $user = $this->create_user_can_tag($course);
+ $formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []);
+
+ $this->setUser($user);
+ $CFG->usetags = false;
+ $result = submit_tags::execute($question->id, $editingcontext->id, $formdata);
+ $CFG->usetags = true;
+
+ $this->assertFalse($result['status']);
+ }
+
+ /**
+ * submit_tags_form should return false if the user does not have any capability
+ * to tag the question.
+ */
+ public function test_submit_tags_form_no_tag_permissions() {
+ global $DB;
+
+ $generator = $this->getDataGenerator();
+ $user = $generator->create_user();
+ $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
+ $questiongenerator = $generator->get_plugin_generator('core_question');
+ list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
+ $questioncontext = \context::instance_by_id($qcat->contextid);
+ $editingcontext = $questioncontext;
+ $question = $questions[0];
+ $formdata = $this->generate_encoded_submit_tags_form_string(
+ $question,
+ $qcat,
+ $questioncontext,
+ ['foo'],
+ ['bar']
+ );
+
+ // Prohibit all of the tag capabilities.
+ assign_capability('moodle/question:tagmine', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id);
+ assign_capability('moodle/question:tagall', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id);
+
+ $generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual');
+ $user->ignoresesskey = true;
+ $this->setUser($user);
+
+ $result = submit_tags::execute($question->id, $editingcontext->id, $formdata);
+
+ $this->assertFalse($result['status']);
+ }
+
+ /**
+ * submit_tags_form should return false if the user only has the capability to
+ * tag their own questions and the question is not theirs.
+ */
+ public function test_submit_tags_form_tagmine_permission_non_owner_question() {
+ global $DB;
+
+ $generator = $this->getDataGenerator();
+ $user = $generator->create_user();
+ $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
+ $questiongenerator = $generator->get_plugin_generator('core_question');
+ list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
+ $questioncontext = \context::instance_by_id($qcat->contextid);
+ $editingcontext = $questioncontext;
+ $question = $questions[0];
+ $formdata = $this->generate_encoded_submit_tags_form_string(
+ $question,
+ $qcat,
+ $questioncontext,
+ ['foo'],
+ ['bar']
+ );
+
+ // Make sure the question isn't created by the user.
+ $question->createdby = $user->id + 1;
+
+ // Prohibit all of the tag capabilities.
+ assign_capability('moodle/question:tagmine', CAP_ALLOW, $teacherrole->id, $questioncontext->id);
+ assign_capability('moodle/question:tagall', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id);
+
+ $generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual');
+ $user->ignoresesskey = true;
+ $this->setUser($user);
+
+ $result = submit_tags::execute($question->id, $editingcontext->id, $formdata);
+
+ $this->assertFalse($result['status']);
+ }
+
+ /**
+ * Data provided for the submit_tags_form test to check that course tags are
+ * only created in the correct editing and question context combinations.
+ *
+ * @return array Test cases
+ */
+ public function get_submit_tags_form_testcases() {
+ return [
+ 'course - course' => [
+ 'editingcontext' => 'course',
+ 'questioncontext' => 'course',
+ 'questiontags' => ['foo'],
+ 'coursetags' => ['bar'],
+ 'expectcoursetags' => false
+ ],
+ 'course - course - empty tags' => [
+ 'editingcontext' => 'course',
+ 'questioncontext' => 'course',
+ 'questiontags' => [],
+ 'coursetags' => ['bar'],
+ 'expectcoursetags' => false
+ ],
+ 'course - course category' => [
+ 'editingcontext' => 'course',
+ 'questioncontext' => 'category',
+ 'questiontags' => ['foo'],
+ 'coursetags' => ['bar'],
+ 'expectcoursetags' => true
+ ],
+ 'course - system' => [
+ 'editingcontext' => 'course',
+ 'questioncontext' => 'system',
+ 'questiontags' => ['foo'],
+ 'coursetags' => ['bar'],
+ 'expectcoursetags' => true
+ ],
+ 'course category - course' => [
+ 'editingcontext' => 'category',
+ 'questioncontext' => 'course',
+ 'questiontags' => ['foo'],
+ 'coursetags' => ['bar'],
+ 'expectcoursetags' => false
+ ],
+ 'course category - course category' => [
+ 'editingcontext' => 'category',
+ 'questioncontext' => 'category',
+ 'questiontags' => ['foo'],
+ 'coursetags' => ['bar'],
+ 'expectcoursetags' => false
+ ],
+ 'course category - system' => [
+ 'editingcontext' => 'category',
+ 'questioncontext' => 'system',
+ 'questiontags' => ['foo'],
+ 'coursetags' => ['bar'],
+ 'expectcoursetags' => false
+ ],
+ 'system - course' => [
+ 'editingcontext' => 'system',
+ 'questioncontext' => 'course',
+ 'questiontags' => ['foo'],
+ 'coursetags' => ['bar'],
+ 'expectcoursetags' => false
+ ],
+ 'system - course category' => [
+ 'editingcontext' => 'system',
+ 'questioncontext' => 'category',
+ 'questiontags' => ['foo'],
+ 'coursetags' => ['bar'],
+ 'expectcoursetags' => false
+ ],
+ 'system - system' => [
+ 'editingcontext' => 'system',
+ 'questioncontext' => 'system',
+ 'questiontags' => ['foo'],
+ 'coursetags' => ['bar'],
+ 'expectcoursetags' => false
+ ],
+ ];
+ }
+
+ /**
+ * Tests that submit_tags_form only creates course tags when the correct combination
+ * of editing context and question context is provided.
+ *
+ * Course tags can only be set on a course category or system context question that
+ * is being editing in a course context.
+ *
+ * @dataProvider get_submit_tags_form_testcases()
+ * @param string $editingcontext The type of the context the question is being edited in
+ * @param string $questioncontext The type of the context the question belongs to
+ * @param string[] $questiontags The tag names to set as question tags
+ * @param string[] $coursetags The tag names to set as course tags
+ * @param bool $expectcoursetags If the given course tags should have been set or not
+ */
+ public function test_submit_tags_form_context_combinations(
+ $editingcontext,
+ $questioncontext,
+ $questiontags,
+ $coursetags,
+ $expectcoursetags
+ ) {
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions($questioncontext);
+ $coursecontext = \context_course::instance($course->id);
+ $questioncontext = \context::instance_by_id($qcat->contextid);
+
+ switch($editingcontext) {
+ case 'system':
+ $editingcontext = \context_system::instance();
+ break;
+
+ case 'category':
+ $editingcontext = \context_coursecat::instance($category->id);
+ break;
+
+ default:
+ $editingcontext = \context_course::instance($course->id);
+ }
+
+ $user = $this->create_user_can_tag($course);
+ $question = $questions[0];
+ $formdata = $this->generate_encoded_submit_tags_form_string(
+ $question,
+ $qcat,
+ $questioncontext,
+ $questiontags, // Question tags.
+ $coursetags // Course tags.
+ );
+
+ $this->setUser($user);
+
+ $result = submit_tags::execute($question->id, $editingcontext->id, $formdata);
+
+ $this->assertTrue($result['status']);
+
+ $tagobjects = \core_tag_tag::get_item_tags('core_question', 'question', $question->id);
+ $coursetagobjects = [];
+ $questiontagobjects = [];
+
+ if ($expectcoursetags) {
+ // If the use case is expecting course tags to be created then split
+ // the tags into course tags and question tags and ensure we have
+ // the correct number of course tags.
+
+ while ($tagobject = array_shift($tagobjects)) {
+ if ($tagobject->taginstancecontextid == $questioncontext->id) {
+ $questiontagobjects[] = $tagobject;
+ } else if ($tagobject->taginstancecontextid == $coursecontext->id) {
+ $coursetagobjects[] = $tagobject;
+ }
+ }
+
+ $this->assertCount(count($coursetags), $coursetagobjects);
+ } else {
+ $questiontagobjects = $tagobjects;
+ }
+
+ // Ensure the expected number of question tags was created.
+ $this->assertCount(count($questiontags), $questiontagobjects);
+
+ foreach ($questiontagobjects as $tagobject) {
+ // If we have any question tags then make sure they are in the list
+ // of expected tags and have the correct context.
+ $this->assertContains($tagobject->name, $questiontags);
+ $this->assertEquals($questioncontext->id, $tagobject->taginstancecontextid);
+ }
+
+ foreach ($coursetagobjects as $tagobject) {
+ // If we have any course tags then make sure they are in the list
+ // of expected course tags and have the correct context.
+ $this->assertContains($tagobject->name, $coursetags);
+ $this->assertEquals($coursecontext->id, $tagobject->taginstancecontextid);
+ }
+ }
+
+ /**
+ * Build the encoded form data expected by the submit_tags_form external function.
+ *
+ * @param \stdClass $question The question record
+ * @param \stdClass $questioncategory The question category record
+ * @param \context $questioncontext Context for the question category
+ * @param array $tags A list of tag names for the question
+ * @param array $coursetags A list of course tag names for the question
+ * @return string HTML encoded string of the data
+ */
+ protected function generate_encoded_submit_tags_form_string($question, $questioncategory,
+ $questioncontext, $tags = [], $coursetags = []) {
+
+ $data = [
+ 'id' => $question->id,
+ 'categoryid' => $questioncategory->id,
+ 'contextid' => $questioncontext->id,
+ 'questionname' => $question->name,
+ 'questioncategory' => $questioncategory->name,
+ 'context' => $questioncontext->get_context_name(false),
+ 'tags' => $tags,
+ 'coursetags' => $coursetags
+ ];
+ $data = \qbank_tagquestion\form\tags_form::mock_generate_submit_keys($data);
+
+ return http_build_query($data, '', '&');
+ }
+
+ /**
+ * Create a user, enrol them in the course, and give them the capability to
+ * tag all questions in the system context.
+ *
+ * @param \stdClass $course The course record to enrol in
+ * @return \stdClass The user record
+ */
+ protected function create_user_can_tag($course) {
+ global $DB;
+
+ $generator = $this->getDataGenerator();
+ $user = $generator->create_user();
+ $roleid = $generator->create_role();
+ $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
+ $systemcontext = \context_system::instance();
+
+ $generator->role_assign($roleid, $user->id, $systemcontext->id);
+ $generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual');
+
+ // Give the user global ability to tag questions.
+ assign_capability('moodle/question:tagall', CAP_ALLOW, $roleid, $systemcontext, true);
+ // Allow the user to submit form data.
+ $user->ignoresesskey = true;
+
+ return $user;
+ }
+
+}
diff --git a/question/bank/tagquestion/version.php b/question/bank/tagquestion/version.php
new file mode 100644
index 0000000000000..2709228d67083
--- /dev/null
+++ b/question/bank/tagquestion/version.php
@@ -0,0 +1,31 @@
+.
+
+/**
+ * Version information for qbank_tagquestion.
+ *
+ * @package qbank_tagquestion
+ * @copyright 2021 Catalyst IT Australia Pty Ltd
+ * @author Safat Shahin
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->component = 'qbank_tagquestion';
+$plugin->version = 2021090300;
+$plugin->requires = 2021052500;
+$plugin->maturity = MATURITY_STABLE;
diff --git a/question/classes/bank/tags_action_column.php b/question/classes/bank/tags_action_column.php
index 222ac580b3689..991e2315f5452 100644
--- a/question/classes/bank/tags_action_column.php
+++ b/question/classes/bank/tags_action_column.php
@@ -40,11 +40,12 @@ class tags_action_column extends action_column_base implements menuable_action {
public function init() {
parent::init();
- global $CFG;
- if ($CFG->usetags) {
- global $PAGE;
- $PAGE->requires->js_call_amd('core_question/edit_tags', 'init', ['#questionscontainer']);
- }
+ // Removed for conflicting js calls.
+ // ...global $CFG;.
+ // ...if ($CFG->usetags) {.
+ // ...global $PAGE;.
+ // ...$PAGE->requires->js_call_amd('core_question/edit_tags', 'init', ['#questionscontainer']);.
+ // ...}.
$this->managetags = get_string('managetags', 'tag');
}
diff --git a/question/classes/external.php b/question/classes/external.php
index 9bda45d7c543b..3a5661d123184 100644
--- a/question/classes/external.php
+++ b/question/classes/external.php
@@ -121,6 +121,9 @@ public static function update_flag_returns() {
* Returns description of method parameters.
*
* @return external_function_parameters.
+ * @deprecated since Moodle 4.0
+ * @see \qbank_tagquestion\external\qbank_tagquestion_external
+ * @todo Final deprecation on Moodle 4.4 MDL-72438
*/
public static function submit_tags_form_parameters() {
return new external_function_parameters([
@@ -137,6 +140,9 @@ public static function submit_tags_form_parameters() {
* @param int $contextid The editing context id.
* @param string $formdata The question tag form data in a URI encoded param string
* @return array The created or modified question tag
+ * @deprecated since Moodle 4.0
+ * @see \qbank_tagquestion\external\qbank_tagquestion_external
+ * @todo Final deprecation on Moodle 4.4 MDL-72438
*/
public static function submit_tags_form($questionid, $contextid, $formdata) {
global $DB, $CFG;
@@ -206,6 +212,10 @@ public static function submit_tags_form($questionid, $contextid, $formdata) {
/**
* Returns description of method result value.
+ *
+ * @deprecated since Moodle 4.0
+ * @see \qbank_tagquestion\external\qbank_tagquestion_external
+ * @todo Final deprecation on Moodle 4.4 MDL-72438
*/
public static function submit_tags_form_returns() {
return new external_single_structure([
@@ -213,6 +223,16 @@ public static function submit_tags_form_returns() {
]);
}
+ /**
+ * Marking the method as deprecated.
+ *
+ * @return bool
+ * @todo Final deprecation on Moodle 4.4 MDL-72438
+ */
+ public static function submit_tags_form_is_deprecated() {
+ return true;
+ }
+
/**
* Returns description of method parameters.
*
diff --git a/question/lib.php b/question/lib.php
index 56fda425f95db..a326e1cf087b1 100644
--- a/question/lib.php
+++ b/question/lib.php
@@ -33,8 +33,13 @@
*
* @param array $args Arguments to the form.
* @return null|string The rendered form.
+ * @deprecated since Moodle 4.0
+ * @see /question/bank/qbank_tagquestion/lib.php
+ * @todo Final deprecation on Moodle 4.4 MDL-72438
*/
function core_question_output_fragment_tags_form($args) {
+ debugging('Function core_question_output_fragment_tags_form() is deprecated,
+ please use core_question_output_fragment_tags_form() from qbank_tagquestion instead.', DEBUG_DEVELOPER);
if (!empty($args['id'])) {
global $CFG, $DB;
diff --git a/question/tests/externallib_test.php b/question/tests/externallib_test.php
index 06783ce26de46..19a54df21befd 100644
--- a/question/tests/externallib_test.php
+++ b/question/tests/externallib_test.php
@@ -103,376 +103,6 @@ public function test_core_question_update_flag() {
}
}
- /**
- * submit_tags_form should throw an exception when the question id doesn't match
- * a question.
- */
- public function test_submit_tags_form_incorrect_question_id() {
- $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
- list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
- $questioncontext = context::instance_by_id($qcat->contextid);
- $editingcontext = $questioncontext;
- $question = $questions[0];
- // Generate an id for a question that doesn't exist.
- $missingquestionid = $questions[1]->id * 2;
- $question->id = $missingquestionid;
- $formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []);
-
- // We should receive an exception if the question doesn't exist.
- $this->expectException('moodle_exception');
- core_question_external::submit_tags_form($missingquestionid, $editingcontext->id, $formdata);
- }
-
- /**
- * submit_tags_form should throw an exception when the context id doesn't match
- * a context.
- */
- public function test_submit_tags_form_incorrect_context_id() {
- $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
- list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
- $questioncontext = context::instance_by_id($qcat->contextid);
- $editingcontext = $questioncontext;
- $question = $questions[0];
- // Generate an id for a context that doesn't exist.
- $missingcontextid = $editingcontext->id * 200;
- $formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []);
-
- // We should receive an exception if the question doesn't exist.
- $this->expectException('moodle_exception');
- core_question_external::submit_tags_form($question->id, $missingcontextid, $formdata);
- }
-
- /**
- * submit_tags_form should return false when tags are disabled.
- */
- public function test_submit_tags_form_tags_disabled() {
- global $CFG;
-
- $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
- list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
- $questioncontext = context::instance_by_id($qcat->contextid);
- $editingcontext = $questioncontext;
- $question = $questions[0];
- $user = $this->create_user_can_tag($course);
- $formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []);
-
- $this->setUser($user);
- $CFG->usetags = false;
- $result = core_question_external::submit_tags_form($question->id, $editingcontext->id, $formdata);
- $CFG->usetags = true;
-
- $this->assertFalse($result['status']);
- }
-
- /**
- * submit_tags_form should return false if the user does not have any capability
- * to tag the question.
- */
- public function test_submit_tags_form_no_tag_permissions() {
- global $DB;
-
- $generator = $this->getDataGenerator();
- $user = $generator->create_user();
- $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
- $questiongenerator = $generator->get_plugin_generator('core_question');
- list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
- $questioncontext = context::instance_by_id($qcat->contextid);
- $editingcontext = $questioncontext;
- $question = $questions[0];
- $formdata = $this->generate_encoded_submit_tags_form_string(
- $question,
- $qcat,
- $questioncontext,
- ['foo'],
- ['bar']
- );
-
- // Prohibit all of the tag capabilities.
- assign_capability('moodle/question:tagmine', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id);
- assign_capability('moodle/question:tagall', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id);
-
- $generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual');
- $user->ignoresesskey = true;
- $this->setUser($user);
-
- $result = core_question_external::submit_tags_form($question->id, $editingcontext->id, $formdata);
-
- $this->assertFalse($result['status']);
- }
-
- /**
- * submit_tags_form should return false if the user only has the capability to
- * tag their own questions and the question is not theirs.
- */
- public function test_submit_tags_form_tagmine_permission_non_owner_question() {
- global $DB;
-
- $generator = $this->getDataGenerator();
- $user = $generator->create_user();
- $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
- $questiongenerator = $generator->get_plugin_generator('core_question');
- list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions();
- $questioncontext = context::instance_by_id($qcat->contextid);
- $editingcontext = $questioncontext;
- $question = $questions[0];
- $formdata = $this->generate_encoded_submit_tags_form_string(
- $question,
- $qcat,
- $questioncontext,
- ['foo'],
- ['bar']
- );
-
- // Make sure the question isn't created by the user.
- $question->createdby = $user->id + 1;
-
- // Prohibit all of the tag capabilities.
- assign_capability('moodle/question:tagmine', CAP_ALLOW, $teacherrole->id, $questioncontext->id);
- assign_capability('moodle/question:tagall', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id);
-
- $generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual');
- $user->ignoresesskey = true;
- $this->setUser($user);
-
- $result = core_question_external::submit_tags_form($question->id, $editingcontext->id, $formdata);
-
- $this->assertFalse($result['status']);
- }
-
- /**
- * Data provided for the submit_tags_form test to check that course tags are
- * only created in the correct editing and question context combinations.
- *
- * @return array Test cases
- */
- public function get_submit_tags_form_testcases() {
- return [
- 'course - course' => [
- 'editingcontext' => 'course',
- 'questioncontext' => 'course',
- 'questiontags' => ['foo'],
- 'coursetags' => ['bar'],
- 'expectcoursetags' => false
- ],
- 'course - course - empty tags' => [
- 'editingcontext' => 'course',
- 'questioncontext' => 'course',
- 'questiontags' => [],
- 'coursetags' => ['bar'],
- 'expectcoursetags' => false
- ],
- 'course - course category' => [
- 'editingcontext' => 'course',
- 'questioncontext' => 'category',
- 'questiontags' => ['foo'],
- 'coursetags' => ['bar'],
- 'expectcoursetags' => true
- ],
- 'course - system' => [
- 'editingcontext' => 'course',
- 'questioncontext' => 'system',
- 'questiontags' => ['foo'],
- 'coursetags' => ['bar'],
- 'expectcoursetags' => true
- ],
- 'course category - course' => [
- 'editingcontext' => 'category',
- 'questioncontext' => 'course',
- 'questiontags' => ['foo'],
- 'coursetags' => ['bar'],
- 'expectcoursetags' => false
- ],
- 'course category - course category' => [
- 'editingcontext' => 'category',
- 'questioncontext' => 'category',
- 'questiontags' => ['foo'],
- 'coursetags' => ['bar'],
- 'expectcoursetags' => false
- ],
- 'course category - system' => [
- 'editingcontext' => 'category',
- 'questioncontext' => 'system',
- 'questiontags' => ['foo'],
- 'coursetags' => ['bar'],
- 'expectcoursetags' => false
- ],
- 'system - course' => [
- 'editingcontext' => 'system',
- 'questioncontext' => 'course',
- 'questiontags' => ['foo'],
- 'coursetags' => ['bar'],
- 'expectcoursetags' => false
- ],
- 'system - course category' => [
- 'editingcontext' => 'system',
- 'questioncontext' => 'category',
- 'questiontags' => ['foo'],
- 'coursetags' => ['bar'],
- 'expectcoursetags' => false
- ],
- 'system - system' => [
- 'editingcontext' => 'system',
- 'questioncontext' => 'system',
- 'questiontags' => ['foo'],
- 'coursetags' => ['bar'],
- 'expectcoursetags' => false
- ],
- ];
- }
-
- /**
- * Tests that submit_tags_form only creates course tags when the correct combination
- * of editing context and question context is provided.
- *
- * Course tags can only be set on a course category or system context question that
- * is being editing in a course context.
- *
- * @dataProvider get_submit_tags_form_testcases()
- * @param string $editingcontext The type of the context the question is being edited in
- * @param string $questioncontext The type of the context the question belongs to
- * @param string[] $questiontags The tag names to set as question tags
- * @param string[] $coursetags The tag names to set as course tags
- * @param bool $expectcoursetags If the given course tags should have been set or not
- */
- public function test_submit_tags_form_context_combinations(
- $editingcontext,
- $questioncontext,
- $questiontags,
- $coursetags,
- $expectcoursetags
- ) {
- $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
- list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions($questioncontext);
- $coursecontext = context_course::instance($course->id);
- $questioncontext = context::instance_by_id($qcat->contextid);
-
- switch($editingcontext) {
- case 'system':
- $editingcontext = context_system::instance();
- break;
-
- case 'category':
- $editingcontext = context_coursecat::instance($category->id);
- break;
-
- default:
- $editingcontext = context_course::instance($course->id);
- }
-
- $user = $this->create_user_can_tag($course);
- $question = $questions[0];
- $formdata = $this->generate_encoded_submit_tags_form_string(
- $question,
- $qcat,
- $questioncontext,
- $questiontags, // Question tags.
- $coursetags // Course tags.
- );
-
- $this->setUser($user);
-
- $result = core_question_external::submit_tags_form($question->id, $editingcontext->id, $formdata);
-
- $this->assertTrue($result['status']);
-
- $tagobjects = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
- $coursetagobjects = [];
- $questiontagobjects = [];
-
- if ($expectcoursetags) {
- // If the use case is expecting course tags to be created then split
- // the tags into course tags and question tags and ensure we have
- // the correct number of course tags.
-
- while ($tagobject = array_shift($tagobjects)) {
- if ($tagobject->taginstancecontextid == $questioncontext->id) {
- $questiontagobjects[] = $tagobject;
- } else if ($tagobject->taginstancecontextid == $coursecontext->id) {
- $coursetagobjects[] = $tagobject;
- }
- }
-
- $this->assertCount(count($coursetags), $coursetagobjects);
- } else {
- $questiontagobjects = $tagobjects;
- }
-
- // Ensure the expected number of question tags was created.
- $this->assertCount(count($questiontags), $questiontagobjects);
-
- foreach ($questiontagobjects as $tagobject) {
- // If we have any question tags then make sure they are in the list
- // of expected tags and have the correct context.
- $this->assertContains($tagobject->name, $questiontags);
- $this->assertEquals($questioncontext->id, $tagobject->taginstancecontextid);
- }
-
- foreach ($coursetagobjects as $tagobject) {
- // If we have any course tags then make sure they are in the list
- // of expected course tags and have the correct context.
- $this->assertContains($tagobject->name, $coursetags);
- $this->assertEquals($coursecontext->id, $tagobject->taginstancecontextid);
- }
- }
-
- /**
- * Build the encoded form data expected by the submit_tags_form external function.
- *
- * @param stdClass $question The question record
- * @param stdClass $questioncategory The question category record
- * @param context $questioncontext Context for the question category
- * @param array $tags A list of tag names for the question
- * @param array $coursetags A list of course tag names for the question
- * @return string HTML encoded string of the data
- */
- protected function generate_encoded_submit_tags_form_string($question, $questioncategory,
- $questioncontext, $tags = [], $coursetags = []) {
- global $CFG;
-
- require_once($CFG->dirroot . '/question/type/tags_form.php');
-
- $data = [
- 'id' => $question->id,
- 'categoryid' => $questioncategory->id,
- 'contextid' => $questioncontext->id,
- 'questionname' => $question->name,
- 'questioncategory' => $questioncategory->name,
- 'context' => $questioncontext->get_context_name(false),
- 'tags' => $tags,
- 'coursetags' => $coursetags
- ];
- $data = core_question\form\tags::mock_generate_submit_keys($data);
-
- return http_build_query($data, '', '&');
- }
-
- /**
- * Create a user, enrol them in the course, and give them the capability to
- * tag all questions in the system context.
- *
- * @param stdClass $course The course record to enrol in
- * @return stdClass The user record
- */
- protected function create_user_can_tag($course) {
- global $DB;
-
- $generator = $this->getDataGenerator();
- $user = $generator->create_user();
- $roleid = $generator->create_role();
- $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
- $systemcontext = context_system::instance();
-
- $generator->role_assign($roleid, $user->id, $systemcontext->id);
- $generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual');
-
- // Give the user global ability to tag questions.
- assign_capability('moodle/question:tagall', CAP_ALLOW, $roleid, $systemcontext, true);
- // Allow the user to submit form data.
- $user->ignoresesskey = true;
-
- return $user;
- }
-
/**
* Data provider for the get_random_question_summaries test.
*/
diff --git a/question/type/edit_question_form.php b/question/type/edit_question_form.php
index 384e689128b8a..b408b2e46a770 100644
--- a/question/type/edit_question_form.php
+++ b/question/type/edit_question_form.php
@@ -216,7 +216,9 @@ protected function definition() {
// Any questiontype specific fields.
$this->definition_inner($mform);
- if (core_tag_tag::is_enabled('core_question', 'question')) {
+ if (core_tag_tag::is_enabled('core_question', 'question')
+ && class_exists('qbank_tagquestion\\tags_action_column')
+ && \core\plugininfo\qbank::is_plugin_enabled('qbank_tagquestion')) {
$this->add_tag_fields($mform);
}
diff --git a/question/type/tags_form.php b/question/type/tags_form.php
index cca587f614aec..d3c22c8630cb2 100644
--- a/question/type/tags_form.php
+++ b/question/type/tags_form.php
@@ -33,6 +33,9 @@
*
* @copyright 2018 Simey Lameze
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @deprecated since Moodle 4.0
+ * @see \qbank_tagquestion\form\tags_form
+ * @todo MDL-71679 class renaming
*/
class tags extends \moodleform {
@@ -40,6 +43,8 @@ class tags extends \moodleform {
* The form definition
*/
public function definition() {
+ debugging('Class column_base in core_question\form\tags is deprecated,
+ please use qbank_tagquestion\form\tags_form instead.', DEBUG_DEVELOPER);
$mform = $this->_form;
$customdata = $this->_customdata;
diff --git a/question/upgrade.txt b/question/upgrade.txt
index f2b7fedbf61b2..6d76a66d63d13 100644
--- a/question/upgrade.txt
+++ b/question/upgrade.txt
@@ -6,6 +6,8 @@ This files describes API changes for code that uses the question API.
are divided in two different parts, base classes and feature classes. All the base
classes are moved classes/local/bank and all the feature classes will be moved to
the plugin for that feature.
+2) submit_tags_form and associated external services for question tag, tags_form in question/type,
+ core_question_output_fragment_tags_form method in lib is deprecated and moved to the tagquestion plugin.
=== 3.9 ==