From d057af03e9ce9e9357ae604e54a40b9726bbb95e Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Thu, 19 Oct 2023 21:50:17 +0100 Subject: [PATCH 1/2] MDL-79788 tag: convert tag collection page to reportbuilder report. --- lib/amd/build/tag.min.js | 4 +- lib/amd/build/tag.min.js.map | 2 +- lib/amd/src/tag.js | 76 +++--- tag/classes/manage_table.php | 5 +- .../local/systemreports/tags.php | 220 ++++++++++++++++++ tag/manage.php | 101 +++----- tag/tests/behat/delete_tag.feature | 21 +- tag/tests/behat/edit_tag.feature | 51 ++-- tag/tests/behat/flag_tags.feature | 42 ++-- tag/tests/behat/standard_tags.feature | 6 +- tag/upgrade.txt | 1 + 11 files changed, 338 insertions(+), 191 deletions(-) create mode 100644 tag/classes/reportbuilder/local/systemreports/tags.php diff --git a/lib/amd/build/tag.min.js b/lib/amd/build/tag.min.js index ad1a96a3baf..49cbc9865a1 100644 --- a/lib/amd/build/tag.min.js +++ b/lib/amd/build/tag.min.js @@ -1,4 +1,4 @@ -define("core/tag",["exports","jquery","core/ajax","core/notification","core/templates","core/str","core/modal_events","core/pending","core/modal_save_cancel","core/config"],(function(_exports,_jquery,_ajax,Notification,Templates,_str,ModalEvents,_pending,_modal_save_cancel,_config){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("core/tag",["exports","jquery","core/ajax","core/notification","core/templates","core/str","core/modal_events","core/pending","core/modal_save_cancel","core/config","core_reportbuilder/local/selectors"],(function(_exports,_jquery,_ajax,Notification,Templates,_str,ModalEvents,_pending,_modal_save_cancel,_config,reportSelectors){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * AJAX helper for the tag management page. * @@ -6,6 +6,6 @@ define("core/tag",["exports","jquery","core/ajax","core/notification","core/temp * @copyright 2015 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.0 - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.initTagindexPage=_exports.initManagePage=_exports.initManageCollectionsPage=void 0,_jquery=_interopRequireDefault(_jquery),Notification=_interopRequireWildcard(Notification),Templates=_interopRequireWildcard(Templates),ModalEvents=_interopRequireWildcard(ModalEvents),_pending=_interopRequireDefault(_pending),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_config=_interopRequireDefault(_config);const getCheckedTags=form=>form.querySelectorAll('input[data-togglegroup="tags-manage"][data-toggle="slave"]:checked');_exports.initTagindexPage=async()=>{document.addEventListener("click",(async e=>{const targetArea=e.target.closest('a[data-quickload="1"]');if(!targetArea)return;const tagArea=targetArea.closest(".tagarea[data-ta]");if(!tagArea)return;e.preventDefault();const pendingPromise=new _pending.default("core/tag:initTagindexPage"),query=targetArea.search.replace(/^\?/,""),params=Object.fromEntries(new URLSearchParams(query).entries());try{const data=await(tagindex=params,(0,_ajax.call)([{methodname:"core_tag_get_tagindex",args:{tagindex:tagindex}}])[0]),{html:html,js:js}=await Templates.renderForPromise("core_tag/index",data);Templates.replaceNode(tagArea,html,js)}catch(error){Notification.exception(error)}var tagindex;pendingPromise.resolve()}))};_exports.initManagePage=()=>{(0,_jquery.default)("body").on("updated","[data-inplaceeditable]",(function(e){var pendingPromise=new _pending.default("core/tag:initManagePage");if((0,_str.get_strings)([{key:"selecttag",component:"core_tag"},{key:"now",component:"core"}]).then((function(result){(0,_jquery.default)('label[for="tagselect'+e.ajaxreturn.itemid+'"]').html(result[0]),(0,_jquery.default)(e.target).closest("tr").find("td.col-timemodified").html(result[1])})).always(pendingPromise.resolve).catch(Notification.exception),"tagflag"===e.ajaxreturn.itemtype){var row=(0,_jquery.default)(e.target).closest("tr");"0"===e.ajaxreturn.value?row.removeClass("table-warning"):row.addClass("table-warning")}})),document.addEventListener("click",(async e=>{const tagManagementCombine=e.target.closest("#tag-management-combine");tagManagementCombine&&(e.preventDefault(),(async tagManagementCombine=>{const pendingPromise=new _pending.default("core/tag:tag-management-combine"),form=tagManagementCombine.closest("form"),checkedTags=getCheckedTags(form);if(checkedTags.length<=1)return void Notification.alert((0,_str.get_string)("combineselected","tag"),(0,_str.get_string)("selectmultipletags","tag"),(0,_str.get_string)("ok"));const tags=Array.from(checkedTags.values()).map((tag=>{const namedElement=document.querySelector('.inplaceeditable[data-itemtype=tagname][data-itemid="'.concat(tag.value,'"]'));return{id:tag.value,name:namedElement.dataset.value}})),modal=await _modal_save_cancel.default.create({title:(0,_str.get_string)("combineselected","tag"),buttons:{save:(0,_str.get_string)("continue","core")},body:Templates.render("core_tag/combine_tags",{tags:tags}),show:!0,removeOnClose:!0});modal.getRoot().on(ModalEvents.save,(e=>{e.preventDefault();const tempElement=document.createElement("input");tempElement.hidden=!0,tempElement.name=tagManagementCombine.name,form.append(tempElement);var maintag=(0,_jquery.default)("input[name=maintag]:checked","#combinetags_form").val();(0,_jquery.default)("").attr("name","maintag").attr("value",maintag).appendTo(form),form.submit()})),await modal.getBodyPromise();const firstOption=document.querySelector("#combinetags_form input[type=radio]");firstOption.focus(),firstOption.checked=!0,pendingPromise.resolve()})(tagManagementCombine)),e.target.closest('a[data-action="addstandardtag"]')&&(e.preventDefault(),(async()=>{var pendingPromise=new _pending.default("core/tag:addstandardtag");const modal=await _modal_save_cancel.default.create({title:(0,_str.get_string)("addotags","tag"),body:Templates.render("core_tag/add_tags",{actionurl:window.location.href,sesskey:M.cfg.sesskey}),buttons:{save:(0,_str.get_string)("continue","core")},removeOnClose:!0,show:!0});modal.getRoot().on(ModalEvents.save,(e=>{var tagsInput=(0,_jquery.default)(e.currentTarget).find("#id_tagslist"),name=tagsInput.val().trim();tagsInput.val(name);var tagsForm=(0,_jquery.default)("#addtags_form");return tagsForm.on("submit",(function(e){var form=(0,_jquery.default)("#addtags_form");!1===form[0].checkValidity()&&(e.preventDefault(),e.stopPropagation()),form.addClass("was-validated"),(0,_jquery.default)('[data-region="tagslistinput"]').addClass("error");var errorMessage=(0,_jquery.default)("#id_tagslist_error_message");errorMessage.removeAttr("hidden"),errorMessage.addClass("help-block")})),tagsForm.submit(),!1})),await modal.getBodyPromise(),pendingPromise.resolve()})());const bulkActionDeleteButton=e.target.closest("#tag-management-delete");bulkActionDeleteButton&&(e.preventDefault(),(async form=>{if(getCheckedTags(form).length)try{await Notification.saveCancelPromise((0,_str.get_string)("delete"),(0,_str.get_string)("confirmdeletetags","tag"),(0,_str.get_string)("yes"),(0,_str.get_string)("no"));const tempElement=document.createElement("input");tempElement.hidden=!0,tempElement.name="bulkdelete",form.append(tempElement),form.submit()}catch{return}})(bulkActionDeleteButton.closest("form")));const rowDeleteButton=e.target.closest(".tagdelete");rowDeleteButton&&(e.preventDefault(),(async button=>{try{await Notification.saveCancelPromise((0,_str.get_string)("delete"),(0,_str.get_string)("confirmdeletetag","tag"),(0,_str.get_string)("yes"),(0,_str.get_string)("no")),window.location.href=button.href}catch{return}})(rowDeleteButton))})),(0,_jquery.default)("body").on("updatefailed","[data-inplaceeditable][data-itemtype=tagname]",(async e=>{var exception=e.exception,newvalue=e.newvalue,tagid=(0,_jquery.default)(e.target).attr("data-itemid");if("namesalreadybeeingused"===exception.errorcode){e.preventDefault();try{await Notification.saveCancelPromise((0,_str.get_string)("confirm"),(0,_str.get_string)("nameuseddocombine","tag"),(0,_str.get_string)("yes"),(0,_str.get_string)("cancel"));const redirectTarget=new URL(window.location);redirectTarget.searchParams.set("newname",newvalue),redirectTarget.searchParams.set("tagid",tagid),redirectTarget.searchParams.set("action","renamecombine"),redirectTarget.searchParams.set("sesskey",_config.default.sesskey),window.location.href=redirectTarget}catch{return}}}))};_exports.initManageCollectionsPage=()=>{(0,_jquery.default)("body").on("updated","[data-inplaceeditable]",(function(e){var areaid,collid,pendingPromise=new _pending.default("core/tag:initManageCollectionsPage-updated"),ajaxreturn=e.ajaxreturn;"core_tag"===ajaxreturn.component&&"tagareaenable"===ajaxreturn.itemtype&&(areaid=(0,_jquery.default)(this).attr("data-itemid"),(0,_jquery.default)(".tag-collections-table ul[data-collectionid] li[data-areaid="+areaid+"]").hide(),"1"===ajaxreturn.value?((0,_jquery.default)(this).closest("tr").removeClass("dimmed_text"),collid=(0,_jquery.default)(this).closest("tr").find('[data-itemtype="tagareacollection"]').attr("data-value"),(0,_jquery.default)(".tag-collections-table ul[data-collectionid="+collid+"] li[data-areaid="+areaid+"]").show()):(0,_jquery.default)(this).closest("tr").addClass("dimmed_text")),"core_tag"===ajaxreturn.component&&"tagareacollection"===ajaxreturn.itemtype&&(areaid=(0,_jquery.default)(this).attr("data-itemid"),(0,_jquery.default)(".tag-collections-table ul[data-collectionid] li[data-areaid="+areaid+"]").hide(),collid=(0,_jquery.default)(this).attr("data-value"),"1"===(0,_jquery.default)(this).closest("tr").find('[data-itemtype="tagareaenable"]').attr("data-value")&&(0,_jquery.default)(".tag-collections-table ul[data-collectionid="+collid+"] li[data-areaid="+areaid+"]").show()),pendingPromise.resolve()})),document.addEventListener("click",(async e=>{const addTagCollectionNode=e.target.closest(".addtagcoll > a");if(addTagCollectionNode)return e.preventDefault(),void(async link=>{const pendingPromise=new _pending.default("core/tag:initManageCollectionsPage-addtagcoll"),href=link.dataset.url;(await _modal_save_cancel.default.create({title:(0,_str.get_string)("addtagcoll","tag"),buttons:{save:(0,_str.get_string)("create","core")},body:Templates.render("core_tag/add_tag_collection",{actionurl:href,sesskey:M.cfg.sesskey}),removeOnClose:!0,show:!0})).getRoot().on(ModalEvents.save,(e=>{const collectionInput=(0,_jquery.default)(e.currentTarget).find("#addtagcoll_name"),name=collectionInput.val().trim();collectionInput.val(name);const form=(0,_jquery.default)("#addtagcoll_form");return form.on("submit",(function(e){!1===form[0].checkValidity()&&(e.preventDefault(),e.stopPropagation()),form.addClass("was-validated"),(0,_jquery.default)('[data-region="addtagcoll_nameinput"]').addClass("error");const errorMessage=(0,_jquery.default)("#id_addtagcoll_name_error_message");errorMessage.removeAttr("hidden"),errorMessage.addClass("help-block")})),form.submit(),!1})),pendingPromise.resolve()})(addTagCollectionNode);const deleteCollectionButton=e.target.closest(".tag-collections-table .action_delete");deleteCollectionButton&&(e.preventDefault(),(async button=>{try{await Notification.saveCancelPromise((0,_str.get_string)("delete"),(0,_str.get_string)("suredeletecoll","tag",button.dataset.collname),(0,_str.get_string)("yes"),(0,_str.get_string)("no"));const redirectTarget=new URL(button.dataset.url);redirectTarget.searchParams.set("sesskey",_config.default.sesskey),window.location.href=redirectTarget}catch{return}})(deleteCollectionButton))}))}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.initTagindexPage=_exports.initManagePage=_exports.initManageCollectionsPage=void 0,_jquery=_interopRequireDefault(_jquery),Notification=_interopRequireWildcard(Notification),Templates=_interopRequireWildcard(Templates),ModalEvents=_interopRequireWildcard(ModalEvents),_pending=_interopRequireDefault(_pending),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_config=_interopRequireDefault(_config),reportSelectors=_interopRequireWildcard(reportSelectors);const getCheckedTags=root=>root.querySelectorAll('[data-togglegroup="report-select-all"][data-toggle="slave"]:checked');_exports.initTagindexPage=async()=>{document.addEventListener("click",(async e=>{const targetArea=e.target.closest('a[data-quickload="1"]');if(!targetArea)return;const tagArea=targetArea.closest(".tagarea[data-ta]");if(!tagArea)return;e.preventDefault();const pendingPromise=new _pending.default("core/tag:initTagindexPage"),query=targetArea.search.replace(/^\?/,""),params=Object.fromEntries(new URLSearchParams(query).entries());try{const data=await(tagindex=params,(0,_ajax.call)([{methodname:"core_tag_get_tagindex",args:{tagindex:tagindex}}])[0]),{html:html,js:js}=await Templates.renderForPromise("core_tag/index",data);Templates.replaceNode(tagArea,html,js)}catch(error){Notification.exception(error)}var tagindex;pendingPromise.resolve()}))};_exports.initManagePage=()=>{(0,_jquery.default)("body").on("updated","[data-inplaceeditable][data-itemtype=tagflag]",(function(e){(0,_jquery.default)(e.target).closest("tr").toggleClass("table-warning","1"===e.ajaxreturn.value)})),document.addEventListener("click",(async e=>{const tagManagementCombine=e.target.closest("#tag-management-combine");tagManagementCombine&&(e.preventDefault(),(async tagManagementCombine=>{const pendingPromise=new _pending.default("core/tag:tag-management-combine"),form=tagManagementCombine.closest("form"),reportElement=document.querySelector(reportSelectors.regions.report),checkedTags=getCheckedTags(reportElement);if(checkedTags.length<=1)return void Notification.alert((0,_str.getString)("combineselected","tag"),(0,_str.getString)("selectmultipletags","tag"),(0,_str.getString)("ok"));const tags=Array.from(checkedTags.values()).map((tag=>{const namedElement=document.querySelector('.inplaceeditable[data-itemtype=tagname][data-itemid="'.concat(tag.value,'"]'));return{id:tag.value,name:namedElement.dataset.value}})),modal=await _modal_save_cancel.default.create({title:(0,_str.getString)("combineselected","tag"),buttons:{save:(0,_str.getString)("continue","core")},body:Templates.render("core_tag/combine_tags",{tags:tags}),show:!0,removeOnClose:!0});modal.getRoot().on(ModalEvents.save,(e=>{e.preventDefault();const tempElement=document.createElement("input");tempElement.hidden=!0,tempElement.name=tagManagementCombine.name,form.append(tempElement);const tagsElement=document.createElement("input");tagsElement.hidden=!0,tagsElement.name="tagschecked",tagsElement.value=[...checkedTags].map((check=>check.value)).join(","),form.append(tagsElement);var maintag=(0,_jquery.default)("input[name=maintag]:checked","#combinetags_form").val();(0,_jquery.default)("").attr("name","maintag").attr("value",maintag).appendTo(form),form.submit()})),await modal.getBodyPromise();const firstOption=document.querySelector("#combinetags_form input[type=radio]");firstOption.focus(),firstOption.checked=!0,pendingPromise.resolve()})(tagManagementCombine)),e.target.closest('[data-action="addstandardtag"]')&&(e.preventDefault(),(async()=>{var pendingPromise=new _pending.default("core/tag:addstandardtag");const modal=await _modal_save_cancel.default.create({title:(0,_str.getString)("addotags","tag"),body:Templates.render("core_tag/add_tags",{actionurl:window.location.href,sesskey:M.cfg.sesskey}),buttons:{save:(0,_str.getString)("continue","core")},removeOnClose:!0,show:!0});modal.getRoot().on(ModalEvents.save,(e=>{var tagsInput=(0,_jquery.default)(e.currentTarget).find("#id_tagslist"),name=tagsInput.val().trim();tagsInput.val(name);var tagsForm=(0,_jquery.default)("#addtags_form");return tagsForm.on("submit",(function(e){var form=(0,_jquery.default)("#addtags_form");!1===form[0].checkValidity()&&(e.preventDefault(),e.stopPropagation()),form.addClass("was-validated"),(0,_jquery.default)('[data-region="tagslistinput"]').addClass("error");var errorMessage=(0,_jquery.default)("#id_tagslist_error_message");errorMessage.removeAttr("hidden"),errorMessage.addClass("help-block")})),tagsForm.submit(),!1})),await modal.getBodyPromise(),pendingPromise.resolve()})());const bulkActionDeleteButton=e.target.closest("#tag-management-delete");bulkActionDeleteButton&&(e.preventDefault(),(async bulkActionDeleteButton=>{const form=bulkActionDeleteButton.closest("form"),reportElement=document.querySelector(reportSelectors.regions.report),checkedTags=getCheckedTags(reportElement);if(checkedTags.length)try{await Notification.saveCancelPromise((0,_str.getString)("delete"),(0,_str.getString)("confirmdeletetags","tag"),(0,_str.getString)("yes"),(0,_str.getString)("no"));const tempElement=document.createElement("input");tempElement.hidden=!0,tempElement.name=bulkActionDeleteButton.name,form.append(tempElement);const tagsElement=document.createElement("input");tagsElement.hidden=!0,tagsElement.name="tagschecked",tagsElement.value=[...checkedTags].map((check=>check.value)).join(","),form.append(tagsElement),form.submit()}catch{return}})(bulkActionDeleteButton));const rowDeleteButton=e.target.closest(".tagdelete");rowDeleteButton&&(e.preventDefault(),(async button=>{try{await Notification.saveCancelPromise((0,_str.getString)("delete"),(0,_str.getString)("confirmdeletetag","tag"),(0,_str.getString)("yes"),(0,_str.getString)("no")),window.location.href=button.href}catch{return}})(rowDeleteButton))})),(0,_jquery.default)("body").on("updatefailed","[data-inplaceeditable][data-itemtype=tagname]",(async e=>{var exception=e.exception,newvalue=e.newvalue,tagid=(0,_jquery.default)(e.target).attr("data-itemid");if("namesalreadybeeingused"===exception.errorcode){e.preventDefault();try{await Notification.saveCancelPromise((0,_str.getString)("confirm"),(0,_str.getString)("nameuseddocombine","tag"),(0,_str.getString)("yes"),(0,_str.getString)("cancel"));const redirectTarget=new URL(window.location);redirectTarget.searchParams.set("newname",newvalue),redirectTarget.searchParams.set("tagid",tagid),redirectTarget.searchParams.set("action","renamecombine"),redirectTarget.searchParams.set("sesskey",_config.default.sesskey),window.location.href=redirectTarget}catch{return}}}))};_exports.initManageCollectionsPage=()=>{(0,_jquery.default)("body").on("updated","[data-inplaceeditable]",(function(e){var areaid,collid,pendingPromise=new _pending.default("core/tag:initManageCollectionsPage-updated"),ajaxreturn=e.ajaxreturn;"core_tag"===ajaxreturn.component&&"tagareaenable"===ajaxreturn.itemtype&&(areaid=(0,_jquery.default)(this).attr("data-itemid"),(0,_jquery.default)(".tag-collections-table ul[data-collectionid] li[data-areaid="+areaid+"]").hide(),"1"===ajaxreturn.value?((0,_jquery.default)(this).closest("tr").removeClass("dimmed_text"),collid=(0,_jquery.default)(this).closest("tr").find('[data-itemtype="tagareacollection"]').attr("data-value"),(0,_jquery.default)(".tag-collections-table ul[data-collectionid="+collid+"] li[data-areaid="+areaid+"]").show()):(0,_jquery.default)(this).closest("tr").addClass("dimmed_text")),"core_tag"===ajaxreturn.component&&"tagareacollection"===ajaxreturn.itemtype&&(areaid=(0,_jquery.default)(this).attr("data-itemid"),(0,_jquery.default)(".tag-collections-table ul[data-collectionid] li[data-areaid="+areaid+"]").hide(),collid=(0,_jquery.default)(this).attr("data-value"),"1"===(0,_jquery.default)(this).closest("tr").find('[data-itemtype="tagareaenable"]').attr("data-value")&&(0,_jquery.default)(".tag-collections-table ul[data-collectionid="+collid+"] li[data-areaid="+areaid+"]").show()),pendingPromise.resolve()})),document.addEventListener("click",(async e=>{const addTagCollectionNode=e.target.closest(".addtagcoll > a");if(addTagCollectionNode)return e.preventDefault(),void(async link=>{const pendingPromise=new _pending.default("core/tag:initManageCollectionsPage-addtagcoll"),href=link.dataset.url;(await _modal_save_cancel.default.create({title:(0,_str.getString)("addtagcoll","tag"),buttons:{save:(0,_str.getString)("create","core")},body:Templates.render("core_tag/add_tag_collection",{actionurl:href,sesskey:M.cfg.sesskey}),removeOnClose:!0,show:!0})).getRoot().on(ModalEvents.save,(e=>{const collectionInput=(0,_jquery.default)(e.currentTarget).find("#addtagcoll_name"),name=collectionInput.val().trim();collectionInput.val(name);const form=(0,_jquery.default)("#addtagcoll_form");return form.on("submit",(function(e){!1===form[0].checkValidity()&&(e.preventDefault(),e.stopPropagation()),form.addClass("was-validated"),(0,_jquery.default)('[data-region="addtagcoll_nameinput"]').addClass("error");const errorMessage=(0,_jquery.default)("#id_addtagcoll_name_error_message");errorMessage.removeAttr("hidden"),errorMessage.addClass("help-block")})),form.submit(),!1})),pendingPromise.resolve()})(addTagCollectionNode);const deleteCollectionButton=e.target.closest(".tag-collections-table .action_delete");deleteCollectionButton&&(e.preventDefault(),(async button=>{try{await Notification.saveCancelPromise((0,_str.getString)("delete"),(0,_str.getString)("suredeletecoll","tag",button.dataset.collname),(0,_str.getString)("yes"),(0,_str.getString)("no"));const redirectTarget=new URL(button.dataset.url);redirectTarget.searchParams.set("sesskey",_config.default.sesskey),window.location.href=redirectTarget}catch{return}})(deleteCollectionButton))}))}})); //# sourceMappingURL=tag.min.js.map \ No newline at end of file diff --git a/lib/amd/build/tag.min.js.map b/lib/amd/build/tag.min.js.map index a7b8f12e25e..24719af21f2 100644 --- a/lib/amd/build/tag.min.js.map +++ b/lib/amd/build/tag.min.js.map @@ -1 +1 @@ -{"version":3,"file":"tag.min.js","sources":["../src/tag.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * AJAX helper for the tag management page.\n *\n * @module core/tag\n * @copyright 2015 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.0\n */\n\nimport $ from 'jquery';\nimport {call as fetchMany} from 'core/ajax';\nimport * as Notification from 'core/notification';\nimport * as Templates from 'core/templates';\nimport {\n get_string as getString,\n get_strings as getStrings,\n} from 'core/str';\nimport * as ModalEvents from 'core/modal_events';\nimport Pending from 'core/pending';\nimport SaveCancelModal from 'core/modal_save_cancel';\nimport Config from 'core/config';\n\nconst getTagIndex = (tagindex) => fetchMany([{\n methodname: 'core_tag_get_tagindex',\n args: {tagindex}\n}])[0];\n\nconst getCheckedTags = (form) => form.querySelectorAll('input[data-togglegroup=\"tags-manage\"][data-toggle=\"slave\"]:checked');\n\nconst handleCombineRequest = async(tagManagementCombine) => {\n const pendingPromise = new Pending('core/tag:tag-management-combine');\n const form = tagManagementCombine.closest('form');\n const checkedTags = getCheckedTags(form);\n\n if (checkedTags.length <= 1) {\n // We need at least 2 tags to combine them.\n Notification.alert(\n getString('combineselected', 'tag'),\n getString('selectmultipletags', 'tag'),\n getString('ok'),\n );\n\n return;\n }\n\n const tags = Array.from(checkedTags.values()).map((tag) => {\n const namedElement = document.querySelector(`.inplaceeditable[data-itemtype=tagname][data-itemid=\"${tag.value}\"]`);\n return {\n id: tag.value,\n name: namedElement.dataset.value,\n };\n });\n\n const modal = await SaveCancelModal.create({\n title: getString('combineselected', 'tag'),\n buttons: {\n save: getString('continue', 'core'),\n },\n body: Templates.render('core_tag/combine_tags', {tags}),\n show: true,\n removeOnClose: true,\n });\n\n // Handle save event.\n modal.getRoot().on(ModalEvents.save, (e) => {\n e.preventDefault();\n\n // Append this temp element in the form in the tags list, not the form in the modal. Confusing, right?!?\n const tempElement = document.createElement('input');\n tempElement.hidden = true;\n tempElement.name = tagManagementCombine.name;\n form.append(tempElement);\n\n // Get the selected tag from the modal.\n var maintag = $('input[name=maintag]:checked', '#combinetags_form').val();\n // Append this in the tags list form.\n $(\"\").attr('name', 'maintag').attr('value', maintag).appendTo(form);\n // Submit the tags list form.\n form.submit();\n });\n\n await modal.getBodyPromise();\n // Tick the first option.\n const firstOption = document.querySelector('#combinetags_form input[type=radio]');\n firstOption.focus();\n firstOption.checked = true;\n\n pendingPromise.resolve();\n\n return;\n};\n\nconst addStandardTags = async() => {\n var pendingPromise = new Pending('core/tag:addstandardtag');\n\n const modal = await SaveCancelModal.create({\n title: getString('addotags', 'tag'),\n body: Templates.render('core_tag/add_tags', {\n actionurl: window.location.href,\n sesskey: M.cfg.sesskey,\n }),\n buttons: {\n save: getString('continue', 'core'),\n },\n removeOnClose: true,\n show: true,\n });\n\n // Handle save event.\n modal.getRoot().on(ModalEvents.save, (e) => {\n var tagsInput = $(e.currentTarget).find('#id_tagslist');\n var name = tagsInput.val().trim();\n\n // Set the text field's value to the trimmed value.\n tagsInput.val(name);\n\n // Add submit event listener to the form.\n var tagsForm = $('#addtags_form');\n tagsForm.on('submit', function(e) {\n // Validate the form.\n var form = $('#addtags_form');\n if (form[0].checkValidity() === false) {\n e.preventDefault();\n e.stopPropagation();\n }\n form.addClass('was-validated');\n\n // BS2 compatibility.\n $('[data-region=\"tagslistinput\"]').addClass('error');\n var errorMessage = $('#id_tagslist_error_message');\n errorMessage.removeAttr('hidden');\n errorMessage.addClass('help-block');\n });\n\n // Try to submit the form.\n tagsForm.submit();\n\n return false;\n });\n\n await modal.getBodyPromise();\n pendingPromise.resolve();\n};\n\nconst deleteSelectedTags = async(form) => {\n const checkedTags = getCheckedTags(form);\n if (!checkedTags.length) {\n return;\n }\n\n try {\n await Notification.saveCancelPromise(\n getString('delete'),\n getString('confirmdeletetags', 'tag'),\n getString('yes'),\n getString('no'),\n );\n\n // Append this temp element in the form in the tags list, not the form in the modal. Confusing, right?!?\n const tempElement = document.createElement('input');\n tempElement.hidden = true;\n tempElement.name = 'bulkdelete';\n form.append(tempElement);\n form.submit();\n } catch {\n return;\n }\n};\n\nconst deleteSelectedTag = async(button) => {\n try {\n await Notification.saveCancelPromise(\n getString('delete'),\n getString('confirmdeletetag', 'tag'),\n getString('yes'),\n getString('no'),\n );\n\n window.location.href = button.href;\n } catch {\n return;\n }\n};\n\nconst deleteSelectedCollection = async(button) => {\n try {\n await Notification.saveCancelPromise(\n getString('delete'),\n getString('suredeletecoll', 'tag', button.dataset.collname),\n getString('yes'),\n getString('no'),\n );\n\n const redirectTarget = new URL(button.dataset.url);\n redirectTarget.searchParams.set('sesskey', Config.sesskey);\n window.location.href = redirectTarget;\n } catch {\n return;\n }\n};\n\nconst addTagCollection = async(link) => {\n const pendingPromise = new Pending('core/tag:initManageCollectionsPage-addtagcoll');\n const href = link.dataset.url;\n\n const modal = await SaveCancelModal.create({\n title: getString('addtagcoll', 'tag'),\n buttons: {\n save: getString('create', 'core'),\n },\n body: Templates.render('core_tag/add_tag_collection', {\n actionurl: href,\n sesskey: M.cfg.sesskey,\n }),\n removeOnClose: true,\n show: true,\n });\n\n // Handle save event.\n modal.getRoot().on(ModalEvents.save, (e) => {\n const collectionInput = $(e.currentTarget).find('#addtagcoll_name');\n const name = collectionInput.val().trim();\n // Set the text field's value to the trimmed value.\n collectionInput.val(name);\n\n // Add submit event listener to the form.\n const form = $('#addtagcoll_form');\n form.on('submit', function(e) {\n // Validate the form.\n if (form[0].checkValidity() === false) {\n e.preventDefault();\n e.stopPropagation();\n }\n form.addClass('was-validated');\n\n // BS2 compatibility.\n $('[data-region=\"addtagcoll_nameinput\"]').addClass('error');\n const errorMessage = $('#id_addtagcoll_name_error_message');\n errorMessage.removeAttr('hidden');\n errorMessage.addClass('help-block');\n });\n\n // Try to submit the form.\n form.submit();\n\n return false;\n });\n\n pendingPromise.resolve();\n};\n\n/**\n * Initialises tag index page.\n *\n * @method initTagindexPage\n */\nexport const initTagindexPage = async() => {\n document.addEventListener('click', async(e) => {\n const targetArea = e.target.closest('a[data-quickload=\"1\"]');\n if (!targetArea) {\n return;\n }\n const tagArea = targetArea.closest('.tagarea[data-ta]');\n if (!tagArea) {\n return;\n }\n\n e.preventDefault();\n const pendingPromise = new Pending('core/tag:initTagindexPage');\n\n const query = targetArea.search.replace(/^\\?/, '');\n const params = Object.fromEntries((new URLSearchParams(query)).entries());\n\n try {\n const data = await getTagIndex(params);\n const {html, js} = await Templates.renderForPromise('core_tag/index', data);\n Templates.replaceNode(tagArea, html, js);\n } catch (error) {\n Notification.exception(error);\n }\n pendingPromise.resolve();\n });\n};\n\n/**\n * Initialises tag management page.\n *\n * @method initManagePage\n */\nexport const initManagePage = () => {\n // Set cell 'time modified' to 'now' when any of the element is updated in this row.\n $('body').on('updated', '[data-inplaceeditable]', function(e) {\n var pendingPromise = new Pending('core/tag:initManagePage');\n\n getStrings([\n {\n key: 'selecttag',\n component: 'core_tag',\n },\n {\n key: 'now',\n component: 'core',\n },\n ])\n .then(function(result) {\n $('label[for=\"tagselect' + e.ajaxreturn.itemid + '\"]').html(result[0]);\n $(e.target).closest('tr').find('td.col-timemodified').html(result[1]);\n\n return;\n })\n .always(pendingPromise.resolve)\n .catch(Notification.exception);\n\n if (e.ajaxreturn.itemtype === 'tagflag') {\n var row = $(e.target).closest('tr');\n if (e.ajaxreturn.value === '0') {\n row.removeClass('table-warning');\n } else {\n row.addClass('table-warning');\n }\n }\n });\n\n // Confirmation for bulk tag combine button.\n document.addEventListener('click', async(e) => {\n const tagManagementCombine = e.target.closest('#tag-management-combine');\n if (tagManagementCombine) {\n e.preventDefault();\n handleCombineRequest(tagManagementCombine);\n }\n\n if (e.target.closest('a[data-action=\"addstandardtag\"]')) {\n e.preventDefault();\n addStandardTags();\n }\n\n const bulkActionDeleteButton = e.target.closest('#tag-management-delete');\n if (bulkActionDeleteButton) {\n e.preventDefault();\n deleteSelectedTags(bulkActionDeleteButton.closest('form'));\n }\n\n const rowDeleteButton = e.target.closest('.tagdelete');\n if (rowDeleteButton) {\n e.preventDefault();\n deleteSelectedTag(rowDeleteButton);\n }\n });\n\n // When user changes tag name to some name that already exists suggest to combine the tags.\n $('body').on('updatefailed', '[data-inplaceeditable][data-itemtype=tagname]', async(e) => {\n var exception = e.exception; // The exception object returned by the callback.\n var newvalue = e.newvalue; // The value that user tried to udpated the element to.\n var tagid = $(e.target).attr('data-itemid');\n if (exception.errorcode !== 'namesalreadybeeingused') {\n return;\n }\n e.preventDefault(); // This will prevent default error dialogue.\n\n try {\n await Notification.saveCancelPromise(\n getString('confirm'),\n getString('nameuseddocombine', 'tag'),\n getString('yes'),\n getString('cancel'),\n );\n\n // The Promise will resolve on 'Yes' button, and reject on 'Cancel' button.\n const redirectTarget = new URL(window.location);\n redirectTarget.searchParams.set('newname', newvalue);\n redirectTarget.searchParams.set('tagid', tagid);\n redirectTarget.searchParams.set('action', 'renamecombine');\n redirectTarget.searchParams.set('sesskey', Config.sesskey);\n\n window.location.href = redirectTarget;\n } catch {\n return;\n }\n });\n};\n\n/**\n * Initialises tag collection management page.\n *\n * @method initManageCollectionsPage\n */\nexport const initManageCollectionsPage = () => {\n $('body').on('updated', '[data-inplaceeditable]', function(e) {\n var pendingPromise = new Pending('core/tag:initManageCollectionsPage-updated');\n\n var ajaxreturn = e.ajaxreturn,\n areaid, collid, isenabled;\n if (ajaxreturn.component === 'core_tag' && ajaxreturn.itemtype === 'tagareaenable') {\n areaid = $(this).attr('data-itemid');\n $(\".tag-collections-table ul[data-collectionid] li[data-areaid=\" + areaid + \"]\").hide();\n isenabled = ajaxreturn.value;\n if (isenabled === '1') {\n $(this).closest('tr').removeClass('dimmed_text');\n collid = $(this).closest('tr').find('[data-itemtype=\"tagareacollection\"]').attr(\"data-value\");\n $(\".tag-collections-table ul[data-collectionid=\" + collid + \"] li[data-areaid=\" + areaid + \"]\").show();\n } else {\n $(this).closest('tr').addClass('dimmed_text');\n }\n }\n if (ajaxreturn.component === 'core_tag' && ajaxreturn.itemtype === 'tagareacollection') {\n areaid = $(this).attr('data-itemid');\n $(\".tag-collections-table ul[data-collectionid] li[data-areaid=\" + areaid + \"]\").hide();\n collid = $(this).attr('data-value');\n isenabled = $(this).closest('tr').find('[data-itemtype=\"tagareaenable\"]').attr(\"data-value\");\n if (isenabled === \"1\") {\n $(\".tag-collections-table ul[data-collectionid=\" + collid + \"] li[data-areaid=\" + areaid + \"]\").show();\n }\n }\n\n pendingPromise.resolve();\n });\n\n document.addEventListener('click', async(e) => {\n const addTagCollectionNode = e.target.closest('.addtagcoll > a');\n if (addTagCollectionNode) {\n e.preventDefault();\n addTagCollection(addTagCollectionNode);\n return;\n }\n\n const deleteCollectionButton = e.target.closest('.tag-collections-table .action_delete');\n if (deleteCollectionButton) {\n e.preventDefault();\n deleteSelectedCollection(deleteCollectionButton);\n }\n });\n};\n"],"names":["getCheckedTags","form","querySelectorAll","async","document","addEventListener","targetArea","e","target","closest","tagArea","preventDefault","pendingPromise","Pending","query","search","replace","params","Object","fromEntries","URLSearchParams","entries","data","tagindex","methodname","args","html","js","Templates","renderForPromise","replaceNode","error","Notification","exception","resolve","on","key","component","then","result","ajaxreturn","itemid","find","always","catch","itemtype","row","value","removeClass","addClass","tagManagementCombine","checkedTags","length","alert","tags","Array","from","values","map","tag","namedElement","querySelector","id","name","dataset","modal","SaveCancelModal","create","title","buttons","save","body","render","show","removeOnClose","getRoot","ModalEvents","tempElement","createElement","hidden","append","maintag","val","attr","appendTo","submit","getBodyPromise","firstOption","focus","checked","handleCombineRequest","actionurl","window","location","href","sesskey","M","cfg","tagsInput","currentTarget","trim","tagsForm","checkValidity","stopPropagation","errorMessage","removeAttr","addStandardTags","bulkActionDeleteButton","saveCancelPromise","deleteSelectedTags","rowDeleteButton","button","deleteSelectedTag","newvalue","tagid","errorcode","redirectTarget","URL","searchParams","set","Config","areaid","collid","this","hide","addTagCollectionNode","link","url","collectionInput","addTagCollection","deleteCollectionButton","collname","deleteSelectedCollection"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"tag.min.js","sources":["../src/tag.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * AJAX helper for the tag management page.\n *\n * @module core/tag\n * @copyright 2015 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.0\n */\n\nimport $ from 'jquery';\nimport {call as fetchMany} from 'core/ajax';\nimport * as Notification from 'core/notification';\nimport * as Templates from 'core/templates';\nimport {getString} from 'core/str';\nimport * as ModalEvents from 'core/modal_events';\nimport Pending from 'core/pending';\nimport SaveCancelModal from 'core/modal_save_cancel';\nimport Config from 'core/config';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\n\nconst getTagIndex = (tagindex) => fetchMany([{\n methodname: 'core_tag_get_tagindex',\n args: {tagindex}\n}])[0];\n\nconst getCheckedTags = (root) => root.querySelectorAll('[data-togglegroup=\"report-select-all\"][data-toggle=\"slave\"]:checked');\n\nconst handleCombineRequest = async(tagManagementCombine) => {\n const pendingPromise = new Pending('core/tag:tag-management-combine');\n const form = tagManagementCombine.closest('form');\n\n const reportElement = document.querySelector(reportSelectors.regions.report);\n const checkedTags = getCheckedTags(reportElement);\n\n if (checkedTags.length <= 1) {\n // We need at least 2 tags to combine them.\n Notification.alert(\n getString('combineselected', 'tag'),\n getString('selectmultipletags', 'tag'),\n getString('ok'),\n );\n\n return;\n }\n\n const tags = Array.from(checkedTags.values()).map((tag) => {\n const namedElement = document.querySelector(`.inplaceeditable[data-itemtype=tagname][data-itemid=\"${tag.value}\"]`);\n return {\n id: tag.value,\n name: namedElement.dataset.value,\n };\n });\n\n const modal = await SaveCancelModal.create({\n title: getString('combineselected', 'tag'),\n buttons: {\n save: getString('continue', 'core'),\n },\n body: Templates.render('core_tag/combine_tags', {tags}),\n show: true,\n removeOnClose: true,\n });\n\n // Handle save event.\n modal.getRoot().on(ModalEvents.save, (e) => {\n e.preventDefault();\n\n // Append this temp element in the form in the tags list, not the form in the modal. Confusing, right?!?\n const tempElement = document.createElement('input');\n tempElement.hidden = true;\n tempElement.name = tagManagementCombine.name;\n form.append(tempElement);\n\n // Append selected tags element.\n const tagsElement = document.createElement('input');\n tagsElement.hidden = true;\n tagsElement.name = 'tagschecked';\n tagsElement.value = [...checkedTags].map(check => check.value).join(',');\n form.append(tagsElement);\n\n // Get the selected tag from the modal.\n var maintag = $('input[name=maintag]:checked', '#combinetags_form').val();\n // Append this in the tags list form.\n $(\"\").attr('name', 'maintag').attr('value', maintag).appendTo(form);\n // Submit the tags list form.\n form.submit();\n });\n\n await modal.getBodyPromise();\n // Tick the first option.\n const firstOption = document.querySelector('#combinetags_form input[type=radio]');\n firstOption.focus();\n firstOption.checked = true;\n\n pendingPromise.resolve();\n\n return;\n};\n\nconst addStandardTags = async() => {\n var pendingPromise = new Pending('core/tag:addstandardtag');\n\n const modal = await SaveCancelModal.create({\n title: getString('addotags', 'tag'),\n body: Templates.render('core_tag/add_tags', {\n actionurl: window.location.href,\n sesskey: M.cfg.sesskey,\n }),\n buttons: {\n save: getString('continue', 'core'),\n },\n removeOnClose: true,\n show: true,\n });\n\n // Handle save event.\n modal.getRoot().on(ModalEvents.save, (e) => {\n var tagsInput = $(e.currentTarget).find('#id_tagslist');\n var name = tagsInput.val().trim();\n\n // Set the text field's value to the trimmed value.\n tagsInput.val(name);\n\n // Add submit event listener to the form.\n var tagsForm = $('#addtags_form');\n tagsForm.on('submit', function(e) {\n // Validate the form.\n var form = $('#addtags_form');\n if (form[0].checkValidity() === false) {\n e.preventDefault();\n e.stopPropagation();\n }\n form.addClass('was-validated');\n\n // BS2 compatibility.\n $('[data-region=\"tagslistinput\"]').addClass('error');\n var errorMessage = $('#id_tagslist_error_message');\n errorMessage.removeAttr('hidden');\n errorMessage.addClass('help-block');\n });\n\n // Try to submit the form.\n tagsForm.submit();\n\n return false;\n });\n\n await modal.getBodyPromise();\n pendingPromise.resolve();\n};\n\nconst deleteSelectedTags = async(bulkActionDeleteButton) => {\n const form = bulkActionDeleteButton.closest('form');\n\n const reportElement = document.querySelector(reportSelectors.regions.report);\n const checkedTags = getCheckedTags(reportElement);\n\n if (!checkedTags.length) {\n return;\n }\n\n try {\n await Notification.saveCancelPromise(\n getString('delete'),\n getString('confirmdeletetags', 'tag'),\n getString('yes'),\n getString('no'),\n );\n\n // Append this temp element in the form in the tags list, not the form in the modal. Confusing, right?!?\n const tempElement = document.createElement('input');\n tempElement.hidden = true;\n tempElement.name = bulkActionDeleteButton.name;\n form.append(tempElement);\n\n // Append selected tags element.\n const tagsElement = document.createElement('input');\n tagsElement.hidden = true;\n tagsElement.name = 'tagschecked';\n tagsElement.value = [...checkedTags].map(check => check.value).join(',');\n form.append(tagsElement);\n\n form.submit();\n } catch {\n return;\n }\n};\n\nconst deleteSelectedTag = async(button) => {\n try {\n await Notification.saveCancelPromise(\n getString('delete'),\n getString('confirmdeletetag', 'tag'),\n getString('yes'),\n getString('no'),\n );\n\n window.location.href = button.href;\n } catch {\n return;\n }\n};\n\nconst deleteSelectedCollection = async(button) => {\n try {\n await Notification.saveCancelPromise(\n getString('delete'),\n getString('suredeletecoll', 'tag', button.dataset.collname),\n getString('yes'),\n getString('no'),\n );\n\n const redirectTarget = new URL(button.dataset.url);\n redirectTarget.searchParams.set('sesskey', Config.sesskey);\n window.location.href = redirectTarget;\n } catch {\n return;\n }\n};\n\nconst addTagCollection = async(link) => {\n const pendingPromise = new Pending('core/tag:initManageCollectionsPage-addtagcoll');\n const href = link.dataset.url;\n\n const modal = await SaveCancelModal.create({\n title: getString('addtagcoll', 'tag'),\n buttons: {\n save: getString('create', 'core'),\n },\n body: Templates.render('core_tag/add_tag_collection', {\n actionurl: href,\n sesskey: M.cfg.sesskey,\n }),\n removeOnClose: true,\n show: true,\n });\n\n // Handle save event.\n modal.getRoot().on(ModalEvents.save, (e) => {\n const collectionInput = $(e.currentTarget).find('#addtagcoll_name');\n const name = collectionInput.val().trim();\n // Set the text field's value to the trimmed value.\n collectionInput.val(name);\n\n // Add submit event listener to the form.\n const form = $('#addtagcoll_form');\n form.on('submit', function(e) {\n // Validate the form.\n if (form[0].checkValidity() === false) {\n e.preventDefault();\n e.stopPropagation();\n }\n form.addClass('was-validated');\n\n // BS2 compatibility.\n $('[data-region=\"addtagcoll_nameinput\"]').addClass('error');\n const errorMessage = $('#id_addtagcoll_name_error_message');\n errorMessage.removeAttr('hidden');\n errorMessage.addClass('help-block');\n });\n\n // Try to submit the form.\n form.submit();\n\n return false;\n });\n\n pendingPromise.resolve();\n};\n\n/**\n * Initialises tag index page.\n *\n * @method initTagindexPage\n */\nexport const initTagindexPage = async() => {\n document.addEventListener('click', async(e) => {\n const targetArea = e.target.closest('a[data-quickload=\"1\"]');\n if (!targetArea) {\n return;\n }\n const tagArea = targetArea.closest('.tagarea[data-ta]');\n if (!tagArea) {\n return;\n }\n\n e.preventDefault();\n const pendingPromise = new Pending('core/tag:initTagindexPage');\n\n const query = targetArea.search.replace(/^\\?/, '');\n const params = Object.fromEntries((new URLSearchParams(query)).entries());\n\n try {\n const data = await getTagIndex(params);\n const {html, js} = await Templates.renderForPromise('core_tag/index', data);\n Templates.replaceNode(tagArea, html, js);\n } catch (error) {\n Notification.exception(error);\n }\n pendingPromise.resolve();\n });\n};\n\n/**\n * Initialises tag management page.\n *\n * @method initManagePage\n */\nexport const initManagePage = () => {\n // Toggle row class when updating flag.\n $('body').on('updated', '[data-inplaceeditable][data-itemtype=tagflag]', function(e) {\n var row = $(e.target).closest('tr');\n row.toggleClass('table-warning', e.ajaxreturn.value === '1');\n });\n\n // Confirmation for bulk tag combine button.\n document.addEventListener('click', async(e) => {\n const tagManagementCombine = e.target.closest('#tag-management-combine');\n if (tagManagementCombine) {\n e.preventDefault();\n handleCombineRequest(tagManagementCombine);\n }\n\n if (e.target.closest('[data-action=\"addstandardtag\"]')) {\n e.preventDefault();\n addStandardTags();\n }\n\n const bulkActionDeleteButton = e.target.closest('#tag-management-delete');\n if (bulkActionDeleteButton) {\n e.preventDefault();\n deleteSelectedTags(bulkActionDeleteButton);\n }\n\n const rowDeleteButton = e.target.closest('.tagdelete');\n if (rowDeleteButton) {\n e.preventDefault();\n deleteSelectedTag(rowDeleteButton);\n }\n });\n\n // When user changes tag name to some name that already exists suggest to combine the tags.\n $('body').on('updatefailed', '[data-inplaceeditable][data-itemtype=tagname]', async(e) => {\n var exception = e.exception; // The exception object returned by the callback.\n var newvalue = e.newvalue; // The value that user tried to udpated the element to.\n var tagid = $(e.target).attr('data-itemid');\n if (exception.errorcode !== 'namesalreadybeeingused') {\n return;\n }\n e.preventDefault(); // This will prevent default error dialogue.\n\n try {\n await Notification.saveCancelPromise(\n getString('confirm'),\n getString('nameuseddocombine', 'tag'),\n getString('yes'),\n getString('cancel'),\n );\n\n // The Promise will resolve on 'Yes' button, and reject on 'Cancel' button.\n const redirectTarget = new URL(window.location);\n redirectTarget.searchParams.set('newname', newvalue);\n redirectTarget.searchParams.set('tagid', tagid);\n redirectTarget.searchParams.set('action', 'renamecombine');\n redirectTarget.searchParams.set('sesskey', Config.sesskey);\n\n window.location.href = redirectTarget;\n } catch {\n return;\n }\n });\n};\n\n/**\n * Initialises tag collection management page.\n *\n * @method initManageCollectionsPage\n */\nexport const initManageCollectionsPage = () => {\n $('body').on('updated', '[data-inplaceeditable]', function(e) {\n var pendingPromise = new Pending('core/tag:initManageCollectionsPage-updated');\n\n var ajaxreturn = e.ajaxreturn,\n areaid, collid, isenabled;\n if (ajaxreturn.component === 'core_tag' && ajaxreturn.itemtype === 'tagareaenable') {\n areaid = $(this).attr('data-itemid');\n $(\".tag-collections-table ul[data-collectionid] li[data-areaid=\" + areaid + \"]\").hide();\n isenabled = ajaxreturn.value;\n if (isenabled === '1') {\n $(this).closest('tr').removeClass('dimmed_text');\n collid = $(this).closest('tr').find('[data-itemtype=\"tagareacollection\"]').attr(\"data-value\");\n $(\".tag-collections-table ul[data-collectionid=\" + collid + \"] li[data-areaid=\" + areaid + \"]\").show();\n } else {\n $(this).closest('tr').addClass('dimmed_text');\n }\n }\n if (ajaxreturn.component === 'core_tag' && ajaxreturn.itemtype === 'tagareacollection') {\n areaid = $(this).attr('data-itemid');\n $(\".tag-collections-table ul[data-collectionid] li[data-areaid=\" + areaid + \"]\").hide();\n collid = $(this).attr('data-value');\n isenabled = $(this).closest('tr').find('[data-itemtype=\"tagareaenable\"]').attr(\"data-value\");\n if (isenabled === \"1\") {\n $(\".tag-collections-table ul[data-collectionid=\" + collid + \"] li[data-areaid=\" + areaid + \"]\").show();\n }\n }\n\n pendingPromise.resolve();\n });\n\n document.addEventListener('click', async(e) => {\n const addTagCollectionNode = e.target.closest('.addtagcoll > a');\n if (addTagCollectionNode) {\n e.preventDefault();\n addTagCollection(addTagCollectionNode);\n return;\n }\n\n const deleteCollectionButton = e.target.closest('.tag-collections-table .action_delete');\n if (deleteCollectionButton) {\n e.preventDefault();\n deleteSelectedCollection(deleteCollectionButton);\n }\n });\n};\n"],"names":["getCheckedTags","root","querySelectorAll","async","document","addEventListener","targetArea","e","target","closest","tagArea","preventDefault","pendingPromise","Pending","query","search","replace","params","Object","fromEntries","URLSearchParams","entries","data","tagindex","methodname","args","html","js","Templates","renderForPromise","replaceNode","error","Notification","exception","resolve","on","toggleClass","ajaxreturn","value","tagManagementCombine","form","reportElement","querySelector","reportSelectors","regions","report","checkedTags","length","alert","tags","Array","from","values","map","tag","namedElement","id","name","dataset","modal","SaveCancelModal","create","title","buttons","save","body","render","show","removeOnClose","getRoot","ModalEvents","tempElement","createElement","hidden","append","tagsElement","check","join","maintag","val","attr","appendTo","submit","getBodyPromise","firstOption","focus","checked","handleCombineRequest","actionurl","window","location","href","sesskey","M","cfg","tagsInput","currentTarget","find","trim","tagsForm","checkValidity","stopPropagation","addClass","errorMessage","removeAttr","addStandardTags","bulkActionDeleteButton","saveCancelPromise","deleteSelectedTags","rowDeleteButton","button","deleteSelectedTag","newvalue","tagid","errorcode","redirectTarget","URL","searchParams","set","Config","areaid","collid","component","itemtype","this","hide","removeClass","addTagCollectionNode","link","url","collectionInput","addTagCollection","deleteCollectionButton","collname","deleteSelectedCollection"],"mappings":"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"} \ No newline at end of file diff --git a/lib/amd/src/tag.js b/lib/amd/src/tag.js index 042bcdcb4b5..e8c6d98c6d0 100644 --- a/lib/amd/src/tag.js +++ b/lib/amd/src/tag.js @@ -26,26 +26,26 @@ import $ from 'jquery'; import {call as fetchMany} from 'core/ajax'; import * as Notification from 'core/notification'; import * as Templates from 'core/templates'; -import { - get_string as getString, - get_strings as getStrings, -} from 'core/str'; +import {getString} from 'core/str'; import * as ModalEvents from 'core/modal_events'; import Pending from 'core/pending'; import SaveCancelModal from 'core/modal_save_cancel'; import Config from 'core/config'; +import * as reportSelectors from 'core_reportbuilder/local/selectors'; const getTagIndex = (tagindex) => fetchMany([{ methodname: 'core_tag_get_tagindex', args: {tagindex} }])[0]; -const getCheckedTags = (form) => form.querySelectorAll('input[data-togglegroup="tags-manage"][data-toggle="slave"]:checked'); +const getCheckedTags = (root) => root.querySelectorAll('[data-togglegroup="report-select-all"][data-toggle="slave"]:checked'); const handleCombineRequest = async(tagManagementCombine) => { const pendingPromise = new Pending('core/tag:tag-management-combine'); const form = tagManagementCombine.closest('form'); - const checkedTags = getCheckedTags(form); + + const reportElement = document.querySelector(reportSelectors.regions.report); + const checkedTags = getCheckedTags(reportElement); if (checkedTags.length <= 1) { // We need at least 2 tags to combine them. @@ -86,6 +86,13 @@ const handleCombineRequest = async(tagManagementCombine) => { tempElement.name = tagManagementCombine.name; form.append(tempElement); + // Append selected tags element. + const tagsElement = document.createElement('input'); + tagsElement.hidden = true; + tagsElement.name = 'tagschecked'; + tagsElement.value = [...checkedTags].map(check => check.value).join(','); + form.append(tagsElement); + // Get the selected tag from the modal. var maintag = $('input[name=maintag]:checked', '#combinetags_form').val(); // Append this in the tags list form. @@ -157,8 +164,12 @@ const addStandardTags = async() => { pendingPromise.resolve(); }; -const deleteSelectedTags = async(form) => { - const checkedTags = getCheckedTags(form); +const deleteSelectedTags = async(bulkActionDeleteButton) => { + const form = bulkActionDeleteButton.closest('form'); + + const reportElement = document.querySelector(reportSelectors.regions.report); + const checkedTags = getCheckedTags(reportElement); + if (!checkedTags.length) { return; } @@ -174,8 +185,16 @@ const deleteSelectedTags = async(form) => { // Append this temp element in the form in the tags list, not the form in the modal. Confusing, right?!? const tempElement = document.createElement('input'); tempElement.hidden = true; - tempElement.name = 'bulkdelete'; + tempElement.name = bulkActionDeleteButton.name; form.append(tempElement); + + // Append selected tags element. + const tagsElement = document.createElement('input'); + tagsElement.hidden = true; + tagsElement.name = 'tagschecked'; + tagsElement.value = [...checkedTags].map(check => check.value).join(','); + form.append(tagsElement); + form.submit(); } catch { return; @@ -303,37 +322,10 @@ export const initTagindexPage = async() => { * @method initManagePage */ export const initManagePage = () => { - // Set cell 'time modified' to 'now' when any of the element is updated in this row. - $('body').on('updated', '[data-inplaceeditable]', function(e) { - var pendingPromise = new Pending('core/tag:initManagePage'); - - getStrings([ - { - key: 'selecttag', - component: 'core_tag', - }, - { - key: 'now', - component: 'core', - }, - ]) - .then(function(result) { - $('label[for="tagselect' + e.ajaxreturn.itemid + '"]').html(result[0]); - $(e.target).closest('tr').find('td.col-timemodified').html(result[1]); - - return; - }) - .always(pendingPromise.resolve) - .catch(Notification.exception); - - if (e.ajaxreturn.itemtype === 'tagflag') { - var row = $(e.target).closest('tr'); - if (e.ajaxreturn.value === '0') { - row.removeClass('table-warning'); - } else { - row.addClass('table-warning'); - } - } + // Toggle row class when updating flag. + $('body').on('updated', '[data-inplaceeditable][data-itemtype=tagflag]', function(e) { + var row = $(e.target).closest('tr'); + row.toggleClass('table-warning', e.ajaxreturn.value === '1'); }); // Confirmation for bulk tag combine button. @@ -344,7 +336,7 @@ export const initManagePage = () => { handleCombineRequest(tagManagementCombine); } - if (e.target.closest('a[data-action="addstandardtag"]')) { + if (e.target.closest('[data-action="addstandardtag"]')) { e.preventDefault(); addStandardTags(); } @@ -352,7 +344,7 @@ export const initManagePage = () => { const bulkActionDeleteButton = e.target.closest('#tag-management-delete'); if (bulkActionDeleteButton) { e.preventDefault(); - deleteSelectedTags(bulkActionDeleteButton.closest('form')); + deleteSelectedTags(bulkActionDeleteButton); } const rowDeleteButton = e.target.closest('.tagdelete'); diff --git a/tag/classes/manage_table.php b/tag/classes/manage_table.php index 872c09234da..ce636c7510a 100644 --- a/tag/classes/manage_table.php +++ b/tag/classes/manage_table.php @@ -26,6 +26,7 @@ require_once($CFG->libdir . '/tablelib.php'); * @package core_tag * @copyright 2015 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @deprecated since Moodle 4.4 */ class core_tag_manage_table extends table_sql { @@ -43,11 +44,13 @@ class core_tag_manage_table extends table_sql { public function __construct($tagcollid) { global $USER, $PAGE, $OUTPUT; + debugging('core_tag_manage_table is deprecated, please use the new system report', DEBUG_DEVELOPER); + parent::__construct('tag-management-list-'.$USER->id); $this->tagcollid = $tagcollid; - $perpage = optional_param('perpage', DEFAULT_PAGE_SIZE, PARAM_INT); + $perpage = optional_param('perpage', 30, PARAM_INT); $page = optional_param('page', 0, PARAM_INT); $filter = optional_param('filter', '', PARAM_NOTAGS); $baseurl = new moodle_url('/tag/manage.php', array('tc' => $tagcollid, diff --git a/tag/classes/reportbuilder/local/systemreports/tags.php b/tag/classes/reportbuilder/local/systemreports/tags.php new file mode 100644 index 00000000000..5217a68cbb2 --- /dev/null +++ b/tag/classes/reportbuilder/local/systemreports/tags.php @@ -0,0 +1,220 @@ +. + +declare(strict_types=1); + +namespace core_tag\reportbuilder\local\systemreports; + +use core\context\system; +use core_reportbuilder\local\entities\user; +use core_reportbuilder\local\report\{action, column}; +use core_reportbuilder\system_report; +use core_tag\output\{tagflag, tagisstandard, tagname}; +use core_tag\reportbuilder\local\entities\tag; +use lang_string; +use moodle_url; +use pix_icon; +use stdClass; + +/** + * Tags collection system report + * + * @package core_tag + * @copyright 2023 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class tags extends system_report { + + /** + * Report initialisation + */ + protected function initialise(): void { + $tag = new tag(); + $this->add_entity($tag); + + $tagalias = $tag->get_table_alias('tag'); + $this->set_main_table('tag', $tagalias); + + // Base fields required for various callbacks. + $this->add_base_fields("{$tagalias}.id, {$tagalias}.rawname, {$tagalias}.flag, {$tagalias}.tagcollid"); + $this->set_checkbox_toggleall(static function(stdClass $tag): array { + return [$tag->id, get_string('selecttag', 'core_tag', $tag->rawname)]; + }); + + // Limit tags to current collection. + $this->add_base_condition_simple("{$tagalias}.tagcollid", $this->get_parameter('collection', 0, PARAM_INT)); + + // Join the user entity to represent the tag author. + $user = new user(); + $useralias = $user->get_table_alias('user'); + $this->add_entity($user->add_join("LEFT JOIN {user} {$useralias} ON {$useralias}.id = {$tagalias}.userid")); + + $this->add_columns($tag); + $this->add_filters(); + $this->add_actions(); + + $this->set_downloadable(false); + } + + /** + * Report access + * + * @return bool + */ + protected function can_view(): bool { + global $CFG; + + return !empty($CFG->usetags) && has_capability('moodle/tag:manage', system::instance()); + } + + /** + * Report columns + * + * @param tag $tag + */ + public function add_columns(tag $tag): void { + $tagentity = $tag->get_entity_name(); + $tagalias = $tag->get_table_alias('tag'); + + // Name (editable). + $this->add_column((new column( + 'nameeditable', + new lang_string('name', 'core_tag'), + $tagentity, + )) + ->add_fields("{$tagalias}.name, {$tagalias}.rawname, {$tagalias}.tagcollid, {$tagalias}.id") + ->set_type(column::TYPE_TEXT) + ->set_is_sortable(true) + ->set_callback(static function(string $name, stdClass $tag): string { + global $PAGE; + $editable = new tagname($tag); + return $editable->render($PAGE->get_renderer('core')); + }) + ); + + // User. + $this->add_column_from_entity('user:fullnamewithlink'); + + // Count (TODO MDL-76392 use native entity aggregation). + $this->add_column((new column( + 'instancecount', + new lang_string('count', 'core_tag'), + $tagentity, + )) + ->add_field("(SELECT COUNT(*) FROM {tag_instance} WHERE tagid = {$tagalias}.id)", 'instancecount') + ->set_type(column::TYPE_INTEGER) + ->set_is_sortable(true) + ); + + // Flag (editable). + $this->add_column((new column( + 'flageditable', + new lang_string('flag', 'core_tag'), + $tagentity, + )) + ->add_fields("{$tagalias}.flag, {$tagalias}.id") + ->set_type(column::TYPE_BOOLEAN) + ->set_is_sortable(true) + ->set_callback(static function(bool $flag, stdClass $tag): string { + global $PAGE; + $editable = new tagflag($tag); + return $editable->render($PAGE->get_renderer('core')); + }) + ); + + // Time modified. + $this->add_column_from_entity('tag:timemodified') + ->set_callback(fn($timemodified) => format_time(time() - $timemodified)); + + // Standard (editable). + $this->add_column((new column( + 'standardeditable', + new lang_string('standardtag', 'core_tag'), + $tagentity, + )) + ->add_fields("{$tagalias}.isstandard, {$tagalias}.id") + ->set_type(column::TYPE_BOOLEAN) + ->set_is_sortable(true) + ->set_callback(static function(bool $standard, stdClass $tag): string { + global $PAGE; + $editable = new tagisstandard($tag); + return $editable->render($PAGE->get_renderer('core')); + }) + ); + + $this->set_initial_sort_column('tag:flageditable', SORT_DESC); + } + + /** + * Report filters + */ + protected function add_filters(): void { + $this->add_filters_from_entities([ + 'tag:name', + 'tag:standard', + 'tag:flagged', + ]); + } + + /** + * Report actions + */ + protected function add_actions(): void { + + // Edit. + $this->add_action((new action( + new moodle_url('/tag/edit.php', [ + 'id' => ':id', + 'returnurl' => ':returnurl', + ]), + new pix_icon('t/edit', ''), + [], + false, + new lang_string('edit'), + )) + ->add_callback(static function(stdClass $tag): bool { + $tag->returnurl = (new moodle_url('/tag/manage.php', ['tc' => $tag->tagcollid]))->out_as_local_url(false); + return true; + }) + ); + + // Delete. + $this->add_action(new action( + new moodle_url('/tag/manage.php', [ + 'tc' => ':tagcollid', + 'tagid' => ':id', + 'action' => 'delete', + 'sesskey' => sesskey(), + ]), + new pix_icon('t/delete', ''), + [ + 'class' => 'tagdelete text-danger', + ], + false, + new lang_string('delete'), + )); + } + + /** + * Report row class + * + * @param stdClass $row + * @return string + */ + public function get_row_class(stdClass $row): string { + return $row->flag ? 'table-warning' : ''; + } +} diff --git a/tag/manage.php b/tag/manage.php index e4165a80955..144160f44f7 100644 --- a/tag/manage.php +++ b/tag/manage.php @@ -23,36 +23,22 @@ */ require_once('../config.php'); -require_once($CFG->libdir.'/tablelib.php'); require_once('lib.php'); require_once($CFG->libdir.'/adminlib.php'); -define('SHOW_ALL_PAGE_SIZE', 50000); -define('DEFAULT_PAGE_SIZE', 30); +use core\context\system; +use core_reportbuilder\system_report_factory; +use core_tag\reportbuilder\local\systemreports\tags; -$tagschecked = optional_param_array('tagschecked', array(), PARAM_INT); $tagid = optional_param('tagid', null, PARAM_INT); $isstandard = optional_param('isstandard', null, PARAM_INT); $action = optional_param('action', '', PARAM_ALPHA); -$perpage = optional_param('perpage', DEFAULT_PAGE_SIZE, PARAM_INT); -$page = optional_param('page', 0, PARAM_INT); $tagcollid = optional_param('tc', 0, PARAM_INT); -$tagareaid = optional_param('ta', null, PARAM_INT); -$filter = optional_param('filter', '', PARAM_NOTAGS); $params = array(); -if ($perpage != DEFAULT_PAGE_SIZE) { - $params['perpage'] = $perpage; -} -if ($page > 0) { - $params['page'] = $page; -} if ($tagcollid) { $params['tc'] = $tagcollid; } -if ($filter !== '') { - $params['filter'] = $filter; -} admin_externalpage_setup('managetags', '', $params, '', array('pagelayout' => 'report')); @@ -66,7 +52,6 @@ if ($tagid) { $tagcollid = $tagobject->tagcollid; } $tagcoll = core_tag_collection::get_by_id($tagcollid); -$tagarea = core_tag_area::get_by_id($tagareaid); $manageurl = new moodle_url('/tag/manage.php'); if ($tagcoll) { // We are inside a tag collection - add it to the breadcrumb. @@ -125,6 +110,7 @@ switch($action) { break; case 'bulk': + $tagschecked = explode(',', optional_param('tagschecked', '', PARAM_SEQUENCE)); if (optional_param('bulkdelete', null, PARAM_RAW) !== null) { if ($tagschecked) { require_sesskey(); @@ -206,70 +192,39 @@ if (!$tagcoll) { } // Tag collection is specified. Manage tags in this collection. -echo $OUTPUT->heading(core_tag_collection::display_name($tagcoll)); +echo html_writer::div( + $OUTPUT->heading(core_tag_collection::display_name($tagcoll)) . + html_writer::tag( + 'button', + $OUTPUT->pix_icon('t/add', '') . get_string('addotags', 'core_tag'), + [ + 'type' => 'button', + 'class' => 'btn btn-primary my-auto', + 'data-action' => 'addstandardtag', + ], + ), + 'd-flex justify-content-between mb-2', +); -$hiddenfields = [ - (object) ['type' => 'hidden', 'name' => 'tc', 'value' => $tagcollid], - (object) ['type' => 'hidden', 'name' => 'perpage', 'value' => $perpage] -]; +// Render the report. +$report = system_report_factory::create(tags::class, system::instance(), '', '', 0, ['collection' => $tagcoll->id]); +echo $report->output(); -$otherfields = ''; -if ($filter !== '') { - $otherfields = html_writer::link(new moodle_url($PAGE->url, ['filter' => null]), - get_string('resetfilter', 'tag')); -} - -$data = [ - 'action' => new moodle_url('/tag/manage.php'), - 'hiddenfields' => $hiddenfields, - 'inputname' => 'filter', - 'searchstring' => get_string('search'), - 'query' => s($filter), - 'extraclasses' => 'mb-2', - 'otherfields' => $otherfields -]; -echo $OUTPUT->render_from_template('core/search_input', $data); - -// Link to add an standard tags. -$img = $OUTPUT->pix_icon('t/add', ''); -echo '
' . - html_writer::link('#', $img . get_string('addotags', 'tag'), array('data-action' => 'addstandardtag')) . - '
'; - -$table = new core_tag_manage_table($tagcollid); -echo '
'; -echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'tc', 'value' => $tagcollid)); -echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey())); -echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'bulk')); -echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'perpage', 'value' => $perpage)); -echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'page', 'value' => $page)); -echo $table->out($perpage, true); - -if ($table->rawdata) { - echo html_writer::start_tag('p'); +// Render bulk actions. +if ($DB->record_exists('tag', [])) { + echo ''; + echo html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'tc', 'value' => $tagcoll->id]); + echo html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()]); + echo html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'action', 'value' => 'bulk']); echo html_writer::tag('button', get_string('deleteselected', 'tag'), array('id' => 'tag-management-delete', 'type' => 'submit', 'class' => 'tagdeleteselected btn btn-secondary', 'name' => 'bulkdelete')); echo html_writer::tag('button', get_string('combineselected', 'tag'), array('id' => 'tag-management-combine', 'type' => 'submit', 'class' => 'tagcombineselected btn btn-secondary', 'name' => 'bulkcombine')); - echo html_writer::end_tag('p'); + echo '
'; } -echo ''; -$totalcount = $table->totalcount; -if ($perpage == SHOW_ALL_PAGE_SIZE) { - echo html_writer::start_tag('div', array('id' => 'showall')); - $params = array('perpage' => DEFAULT_PAGE_SIZE, 'page' => 0); - $url = new moodle_url($PAGE->url, $params); - echo html_writer::link($url, get_string('showperpage', '', DEFAULT_PAGE_SIZE)); - echo html_writer::end_tag('div'); -} else if ($totalcount > 0 and $perpage < $totalcount) { - echo html_writer::start_tag('div', array('id' => 'showall')); - $params = array('perpage' => SHOW_ALL_PAGE_SIZE, 'page' => 0); - $url = new moodle_url($PAGE->url, $params); - echo html_writer::link($url, get_string('showall', '', $totalcount)); - echo html_writer::end_tag('div'); -} +$PAGE->requires->js_call_amd('core/tag', 'initManagePage'); echo $OUTPUT->footer(); diff --git a/tag/tests/behat/delete_tag.feature b/tag/tests/behat/delete_tag.feature index 0da6773cbf2..008313aa680 100644 --- a/tag/tests/behat/delete_tag.feature +++ b/tag/tests/behat/delete_tag.feature @@ -29,34 +29,17 @@ Feature: Manager is able to delete tags And I should not see "Dog" And I log out - Scenario: Deleting multiple tags with javascript disabled - When I log in as "manager1" - And I navigate to "Appearance > Manage tags" in site administration - And I follow "Default collection" - And I set the following fields to these values: - | Select tag Dog | 1 | - | Select tag Neverusedtag | 1 | - And I press "Delete selected" - Then I should see "Tag(s) deleted" - And I should not see "Dog" - And I should not see "Neverusedtag" - And I follow "Cat" - And I follow "User 1" - And I should see "Cat" - And I should not see "Dog" - And I log out - @javascript Scenario: Deleting a tag with javascript enabled When I log in as "manager1" And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" - And I click on "Delete" "link" in the "Turtle" "table_row" + And I press "Delete" action in the "Turtle" report row Then I should see "Are you sure you want to delete this tag?" And I click on "Cancel" "button" in the "Delete" "dialogue" And I should not see "Tag(s) deleted" And I should see "Turtle" - And I click on "Delete" "link" in the "Dog" "table_row" + And I press "Delete" action in the "Dog" report row And I should see "Are you sure you want to delete this tag?" And I press "Yes" And I should see "Tag(s) deleted" diff --git a/tag/tests/behat/edit_tag.feature b/tag/tests/behat/edit_tag.feature index c9408b22be5..84d2ace856a 100644 --- a/tag/tests/behat/edit_tag.feature +++ b/tag/tests/behat/edit_tag.feature @@ -120,7 +120,7 @@ Feature: Users can edit tags to add description or rename When I log in as "manager1" And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" - And I click on "Edit this tag" "link" in the "Cat" "table_row" + And I press "Edit" action in the "Cat" report row And I set the following fields to these values: | Tag name | Kitten | | Description | Description of tag 1 | @@ -138,7 +138,7 @@ Feature: Users can edit tags to add description or rename When I log in as "manager1" And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" - And I click on "Edit this tag" "link" in the "Cat" "table_row" + And I press "Edit" action in the "Cat" report row And I set the following fields to these values: | Tag name | DOG | And I press "Update" @@ -146,7 +146,7 @@ Feature: Users can edit tags to add description or rename And I set the following fields to these values: | Tag name | Kitten | And I press "Update" - And I click on "Edit this tag" "link" in the "Kitten" "table_row" + And I press "Edit" action in the "Kitten" report row And I set the following fields to these values: | Tag name | KITTEN | And I press "Update" @@ -160,10 +160,10 @@ Feature: Users can edit tags to add description or rename And I follow "Default collection" # Renaming tag to a valid name And I set the field "Edit tag name" in the "Cat" "table_row" to "Kitten" - Then I should not see "Cat" - And "New name for tag" "field" should not exist - And I navigate to "Appearance > Manage tags" in site administration - And I follow "Default collection" + Then the following should not exist in the "reportbuilder-table" table: + | First name | Tag name | + | Admin User | Cat | + And I reload the page And I should see "Kitten" And I should not see "Cat" # Renaming tag to an invalid name @@ -174,22 +174,6 @@ Feature: Users can edit tags to add description or rename And I should see "Turtle" And I should see "Dog" And I should not see "DOG" - And I navigate to "Appearance > Manage tags" in site administration - And I follow "Default collection" - And I should see "Turtle" - And I should see "Dog" - And I should not see "DOG" - # Cancel tag renaming - And I click on "Edit tag name" "link" in the "Dog" "table_row" - And I type "Penguin" - And I press the escape key - And "New name for tag" "field" should not exist - And I should see "Turtle" - And I should not see "Penguin" - And I navigate to "Appearance > Manage tags" in site administration - And I follow "Default collection" - And I should see "Turtle" - And I should not see "Penguin" @javascript Scenario: Combining tags when renaming @@ -238,18 +222,17 @@ Feature: Users can edit tags to add description or rename And I should see "Turtle" And I should not see "Neverusedtag" + @javascript Scenario: Filtering tags When I log in as "manager1" And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" - And I should not see "Reset filter" - And I set the field "Search" to "t" - And I press "Search" - Then the field "Search" matches value "t" - And I should not see "Dog" - And I should see "Cat" - And I should see "Turtle" - And I follow "Reset filter" - And I should see "Dog" - And I should see "Cat" - And I should see "Turtle" + And I click on "Filters" "button" + And I set the following fields in the "Tag name" "core_reportbuilder > Filter" to these values: + | Tag name operator | Is equal to | + | Tag name value | Cat,Dog | + And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element" + Then I should see "Cat" in the "reportbuilder-table" "table" + And I should see "Dog" in the "reportbuilder-table" "table" + And I should not see "Turtle" in the "reportbuilder-table" "table" + And I should not see "Neverusedtag" in the "reportbuilder-table" "table" diff --git a/tag/tests/behat/flag_tags.feature b/tag/tests/behat/flag_tags.feature index bc8348fcf5a..aa062977f3b 100644 --- a/tag/tests/behat/flag_tags.feature +++ b/tag/tests/behat/flag_tags.feature @@ -69,25 +69,35 @@ Feature: Users can flag tags and manager can reset flags And I follow "Default collection" Then "Sweartag" "link" should appear before "Badtag" "link" And "Badtag" "link" should appear before "Nicetag" "link" - And "(2)" "text" should exist in the "//tr[contains(.,'Sweartag')]//td[contains(@class,'col-flag')]" "xpath_element" - And "(1)" "text" should exist in the "//tr[contains(.,'Badtag')]//td[contains(@class,'col-flag')]" "xpath_element" - And "(" "text" should not exist in the "//tr[contains(.,'Nicetag')]//td[contains(@class,'col-flag')]" "xpath_element" - And "(" "text" should not exist in the "//tr[contains(.,'Neverusedtag')]//td[contains(@class,'col-flag')]" "xpath_element" + And the following should exist in the "reportbuilder-table" table: + | Tag name | Flag | + | Sweartag | (2) | + | Badtag | (1) | + And the following should not exist in the "reportbuilder-table" table: + | Tag name | Flag | + | Nicetag | ( | + | Neverusertag | ( | And I click on "Reset flag" "link" in the "Sweartag" "table_row" And I click on "Reset flag" "link" in the "Badtag" "table_row" And I wait until "//tr[contains(.,'Sweartag')]//a[contains(@title,'Flag as inappropriate')]" "xpath_element" exists And I click on "Flag as inappropriate" "link" in the "Sweartag" "table_row" And I click on "Flag as inappropriate" "link" in the "Nicetag" "table_row" - And "(1)" "text" should exist in the "//tr[contains(.,'Sweartag')]//td[contains(@class,'col-flag')]" "xpath_element" - And "(1)" "text" should exist in the "//tr[contains(.,'Nicetag')]//td[contains(@class,'col-flag')]" "xpath_element" - And "(" "text" should not exist in the "//tr[contains(.,'Badtag')]//td[contains(@class,'col-flag')]" "xpath_element" - And "(" "text" should not exist in the "//tr[contains(.,'Neverusedtag')]//td[contains(@class,'col-flag')]" "xpath_element" - And I navigate to "Appearance > Manage tags" in site administration - And I follow "Default collection" - And "Nicetag" "link" should appear before "Sweartag" "link" + And the following should exist in the "reportbuilder-table" table: + | Tag name | Flag | + | Sweartag | (1) | + | Nicetag | (1) | + And the following should not exist in the "reportbuilder-table" table: + | Tag name | Flag | + | Badtag | ( | + | Neverusertag | ( | + And I reload the page + And "Nicetag" "link" should appear before "Neverusedtag" "link" And "Sweartag" "link" should appear before "Badtag" "link" - And "(1)" "text" should exist in the "//tr[contains(.,'Sweartag')]//td[contains(@class,'col-flag')]" "xpath_element" - And "(1)" "text" should exist in the "//tr[contains(.,'Nicetag')]//td[contains(@class,'col-flag')]" "xpath_element" - And "(" "text" should not exist in the "//tr[contains(.,'Badtag')]//td[contains(@class,'col-flag')]" "xpath_element" - And "(" "text" should not exist in the "//tr[contains(.,'Neverusedtag')]//td[contains(@class,'col-flag')]" "xpath_element" - And I log out + And the following should exist in the "reportbuilder-table" table: + | Tag name | Flag | + | Sweartag | (1) | + | Nicetag | (1) | + And the following should not exist in the "reportbuilder-table" table: + | Tag name | Flag | + | Badtag | ( | + | Neverusertag | ( | diff --git a/tag/tests/behat/standard_tags.feature b/tag/tests/behat/standard_tags.feature index dbc7e98bb0f..16c816205ee 100644 --- a/tag/tests/behat/standard_tags.feature +++ b/tag/tests/behat/standard_tags.feature @@ -28,7 +28,7 @@ Feature: Manager can add standard tags and change the tag type of existing tags And "Make standard" "link" should exist in the "Tag1" "table_row" And "Make standard" "link" should exist in the "Tag2" "table_row" And "Remove from standard tags" "link" should exist in the "Tag3" "table_row" - And I follow "Add standard tags" + And I click on "Add standard tags" "button" And I set the field "Enter comma-separated list of new tags" to "Tag1,TAG2,Tag3,Tag4,Tag5" And I press "Continue" And I should see "Standard tag(s) added" @@ -75,12 +75,12 @@ Feature: Manager can add standard tags and change the tag type of existing tags When I log in as "manager1" And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" - And I click on "Edit this tag" "link" in the "Tag1" "table_row" + And I press "Edit" action in the "Tag1" report row And I set the following fields to these values: | Standard | 1 | And I press "Update" Then "Remove from standard tags" "link" should exist in the "Tag1" "table_row" - And I click on "Edit this tag" "link" in the "Tag1" "table_row" + And I press "Edit" action in the "Tag1" report row And I set the following fields to these values: | Standard | 0 | And I press "Update" diff --git a/tag/upgrade.txt b/tag/upgrade.txt index 9ff30c0e2b8..b96213e2552 100644 --- a/tag/upgrade.txt +++ b/tag/upgrade.txt @@ -5,6 +5,7 @@ here is intended especially for developers. * New additional property 'viewurl' has been added to the pre-defined structure in tag_item_exporter. This property represents the URL to view a given tag. +* The `core_tag_manage_table` class has been deprecated, in favour of new report builder implementation === 3.6 === From 27ca0daf8741b2706d66c17d16104698c2684522 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Fri, 20 Oct 2023 15:04:17 +0100 Subject: [PATCH 2/2] MDL-79788 tag: convert module creation of tags to generators. We don't need to use the tags UI directly for creating tags in module scenarios, because it's slow and prone to error in case of interface changes. --- mod/book/tests/behat/edit_tags.feature | 12 +++++------- mod/forum/tests/behat/edit_tags.feature | 12 +++++------- mod/glossary/tests/behat/edit_tags.feature | 15 ++++++--------- mod/wiki/tests/behat/edit_tags.feature | 15 ++++++--------- 4 files changed, 22 insertions(+), 32 deletions(-) diff --git a/mod/book/tests/behat/edit_tags.feature b/mod/book/tests/behat/edit_tags.feature index ac3a03d5f85..7e1f3f14427 100644 --- a/mod/book/tests/behat/edit_tags.feature +++ b/mod/book/tests/behat/edit_tags.feature @@ -40,13 +40,11 @@ Feature: Edited book chapters handle tags correctly @javascript Scenario: Book chapter edition of standard tags works as expected - Given I log in as "admin" - And I change window size to "large" - And I navigate to "Appearance > Manage tags" in site administration - And I follow "Default collection" - And I follow "Add standard tags" - And I set the field "Enter comma-separated list of new tags" to "OT1, OT2, OT3" - And I press "Continue" + Given the following "tags" exist: + | name | isstandard | + | OT1 | 1 | + | OT2 | 1 | + | OT3 | 1 | And I am on the "Test book" "book activity" page logged in as teacher1 And I open the autocomplete suggestions list And I should see "OT1" in the ".form-autocomplete-suggestions" "css_element" diff --git a/mod/forum/tests/behat/edit_tags.feature b/mod/forum/tests/behat/edit_tags.feature index 83a6d60d5a0..50620a92808 100644 --- a/mod/forum/tests/behat/edit_tags.feature +++ b/mod/forum/tests/behat/edit_tags.feature @@ -38,13 +38,11 @@ Feature: Edited forum posts handle tags correctly @javascript Scenario: Forum post edition of standard tags works as expected - Given I log in as "admin" - And I change window size to "large" - And I navigate to "Appearance > Manage tags" in site administration - And I follow "Default collection" - And I follow "Add standard tags" - And I set the field "Enter comma-separated list of new tags" to "OT1, OT2, OT3" - And I press "Continue" + Given the following "tags" exist: + | name | isstandard | + | OT1 | 1 | + | OT2 | 1 | + | OT3 | 1 | And I am on the "Test forum name" "forum activity" page logged in as teacher1 And I click on "Add discussion topic" "link" And I click on "Advanced" "button" diff --git a/mod/glossary/tests/behat/edit_tags.feature b/mod/glossary/tests/behat/edit_tags.feature index 59b2a1a850e..86458963948 100644 --- a/mod/glossary/tests/behat/edit_tags.feature +++ b/mod/glossary/tests/behat/edit_tags.feature @@ -40,15 +40,12 @@ Feature: Edited glossary entries handle tags correctly Then I should see "Cool" in the ".form-autocomplete-selection" "css_element" Scenario: Glossary entry edition of standard tags works as expected - Given I log in as "admin" - And I change window size to "large" - And I navigate to "Appearance > Manage tags" in site administration - And I follow "Default collection" - And I follow "Add standard tags" - And I set the field "Enter comma-separated list of new tags" to "OT1, OT2, OT3" - And I press "Continue" - And I log out - Given I am on the "Test glossary" "glossary activity" page logged in as teacher1 + Given the following "tags" exist: + | name | isstandard | + | OT1 | 1 | + | OT2 | 1 | + | OT3 | 1 | + And I am on the "Test glossary" "glossary activity" page logged in as teacher1 And I press "Add entry" And I expand all fieldsets And I open the autocomplete suggestions list diff --git a/mod/wiki/tests/behat/edit_tags.feature b/mod/wiki/tests/behat/edit_tags.feature index 496c2754a90..2017784615d 100644 --- a/mod/wiki/tests/behat/edit_tags.feature +++ b/mod/wiki/tests/behat/edit_tags.feature @@ -41,15 +41,12 @@ Feature: Edited wiki pages handle tags correctly @javascript Scenario: Wiki page edition of standard tags works as expected - Given I log in as "admin" - And I change window size to "large" - And I navigate to "Appearance > Manage tags" in site administration - And I follow "Default collection" - And I follow "Add standard tags" - And I set the field "Enter comma-separated list of new tags" to "OT1, OT2, OT3" - And I press "Continue" - And I log out - Given I am on the "Test wiki name" "wiki activity" page logged in as student1 + Given the following "tags" exist: + | name | isstandard | + | OT1 | 1 | + | OT2 | 1 | + | OT3 | 1 | + And I am on the "Test wiki name" "wiki activity" page logged in as student1 And I press "Create page" And I open the autocomplete suggestions list And I should see "OT1" in the ".form-autocomplete-suggestions" "css_element"