diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php
index cf49ea22f1f..3a758374d18 100644
--- a/lib/classes/plugin_manager.php
+++ b/lib/classes/plugin_manager.php
@@ -2038,6 +2038,7 @@ class core_plugin_manager {
'recordrtc',
'link',
'html',
+ 'noautolink',
],
'theme' => array(
diff --git a/lib/editor/tiny/plugins/noautolink/amd/build/commands.min.js b/lib/editor/tiny/plugins/noautolink/amd/build/commands.min.js
new file mode 100644
index 00000000000..3cbabef3507
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/build/commands.min.js
@@ -0,0 +1,3 @@
+define("tiny_noautolink/commands",["exports","editor_tiny/utils","core/str","tiny_noautolink/common","tiny_noautolink/noautolink"],(function(_exports,_utils,_str,_common,_noautolink){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[buttonText,errorInvalidURL,infoEmptySelection,infoAddSuccess,infoRemoveSuccess,buttonImage]=await Promise.all([(0,_str.get_string)("buttontitle",_common.component),(0,_str.get_string)("errorinvalidurl",_common.component),(0,_str.get_string)("infoemptyselection",_common.component),(0,_str.get_string)("infoaddsuccess",_common.component),(0,_str.get_string)("inforemovesuccess",_common.component),(0,_utils.getButtonImage)("icon",_common.component)]);return editor=>{const messages={errorInvalidURL:errorInvalidURL,infoEmptySelection:infoEmptySelection,infoAddSuccess:infoAddSuccess,infoRemoveSuccess:infoRemoveSuccess};editor.ui.registry.addIcon(_common.buttonIcon,buttonImage.html),editor.ui.registry.addToggleButton(_common.buttonName,{icon:_common.buttonIcon,tooltip:buttonText,onAction:()=>{(0,_noautolink.handleAction)(editor,messages)},onSetup:(0,_noautolink.toggleActiveState)(editor)}),editor.ui.registry.addMenuItem(_common.buttonName,{icon:_common.buttonIcon,text:buttonText,onAction:()=>{(0,_noautolink.handleAction)(editor,messages)}})}}}));
+
+//# sourceMappingURL=commands.min.js.map
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/amd/build/commands.min.js.map b/lib/editor/tiny/plugins/noautolink/amd/build/commands.min.js.map
new file mode 100644
index 00000000000..5475dd5fc0d
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/build/commands.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"commands.min.js","sources":["../src/commands.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\nimport {getButtonImage} from 'editor_tiny/utils';\nimport {get_string as getString} from 'core/str';\nimport {component, buttonName, buttonIcon} from 'tiny_noautolink/common';\nimport {handleAction, toggleActiveState} from 'tiny_noautolink/noautolink';\n\n/**\n * Tiny noautolink commands.\n *\n * @module tiny_noautolink/commands\n * @copyright 2023 Meirza \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport const getSetup = async() => {\n const [\n buttonText,\n errorInvalidURL,\n infoEmptySelection,\n infoAddSuccess,\n infoRemoveSuccess,\n buttonImage,\n ] = await Promise.all([\n getString('buttontitle', component),\n getString('errorinvalidurl', component),\n getString('infoemptyselection', component),\n getString('infoaddsuccess', component),\n getString('inforemovesuccess', component),\n getButtonImage('icon', component),\n ]);\n\n return (editor) => {\n\n const messages = {\n errorInvalidURL: errorInvalidURL,\n infoEmptySelection: infoEmptySelection,\n infoAddSuccess: infoAddSuccess,\n infoRemoveSuccess: infoRemoveSuccess\n };\n\n // Register the noautolink Icon.\n editor.ui.registry.addIcon(buttonIcon, buttonImage.html);\n\n // Register the noautolink button.\n editor.ui.registry.addToggleButton(buttonName, {\n icon: buttonIcon,\n tooltip: buttonText,\n onAction: () => {\n handleAction(editor, messages);\n },\n onSetup: toggleActiveState(editor),\n });\n\n // Register the noautolink item.\n editor.ui.registry.addMenuItem(buttonName, {\n icon: buttonIcon,\n text: buttonText,\n onAction: () => {\n handleAction(editor, messages);\n },\n });\n };\n};\n"],"names":["async","buttonText","errorInvalidURL","infoEmptySelection","infoAddSuccess","infoRemoveSuccess","buttonImage","Promise","all","component","editor","messages","ui","registry","addIcon","buttonIcon","html","addToggleButton","buttonName","icon","tooltip","onAction","onSetup","addMenuItem","text"],"mappings":"0RA4BwBA,gBAEhBC,WACAC,gBACAC,mBACAC,eACAC,kBACAC,mBACMC,QAAQC,IAAI,EAClB,mBAAU,cAAeC,oBACzB,mBAAU,kBAAmBA,oBAC7B,mBAAU,qBAAsBA,oBAChC,mBAAU,iBAAkBA,oBAC5B,mBAAU,oBAAqBA,oBAC/B,yBAAe,OAAQA,4BAGnBC,eAEEC,SAAW,CACbT,gBAAiBA,gBACjBC,mBAAoBA,mBACpBC,eAAgBA,eAChBC,kBAAmBA,mBAIvBK,OAAOE,GAAGC,SAASC,QAAQC,mBAAYT,YAAYU,MAGnDN,OAAOE,GAAGC,SAASI,gBAAgBC,mBAAY,CAC3CC,KAAMJ,mBACNK,QAASnB,WACToB,SAAU,kCACOX,OAAQC,WAEzBW,SAAS,iCAAkBZ,UAI/BA,OAAOE,GAAGC,SAASU,YAAYL,mBAAY,CACvCC,KAAMJ,mBACNS,KAAMvB,WACNoB,SAAU,kCACOX,OAAQC"}
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/amd/build/common.min.js b/lib/editor/tiny/plugins/noautolink/amd/build/common.min.js
new file mode 100644
index 00000000000..5c14c0cdf23
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/build/common.min.js
@@ -0,0 +1,3 @@
+define("tiny_noautolink/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={pluginName:"tiny_noautolink/plugin",component:"tiny_noautolink",buttonName:"tiny_noautolink",buttonIcon:"tiny_noautolink"},_exports.default}));
+
+//# sourceMappingURL=common.min.js.map
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/amd/build/common.min.js.map b/lib/editor/tiny/plugins/noautolink/amd/build/common.min.js.map
new file mode 100644
index 00000000000..a1a56f191c0
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/build/common.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"common.min.js","sources":["../src/common.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 * Tiny noautolink common values.\n *\n * @module tiny_noautolink/common\n * @copyright 2023 Meirza \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n pluginName: 'tiny_noautolink/plugin',\n component: 'tiny_noautolink',\n buttonName: 'tiny_noautolink',\n buttonIcon: 'tiny_noautolink',\n};\n"],"names":["pluginName","component","buttonName","buttonIcon"],"mappings":"wKAuBe,CACXA,WAAY,yBACZC,UAAW,kBACXC,WAAY,kBACZC,WAAY"}
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/amd/build/configuration.min.js b/lib/editor/tiny/plugins/noautolink/amd/build/configuration.min.js
new file mode 100644
index 00000000000..93a8920f149
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/build/configuration.min.js
@@ -0,0 +1,3 @@
+define("tiny_noautolink/configuration",["exports","./common","editor_tiny/utils"],(function(_exports,_common,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.configure=void 0;_exports.configure=instanceConfig=>({toolbar:(0,_utils.addToolbarButton)(instanceConfig.toolbar,"content",_common.component),menu:(0,_utils.addMenubarItem)(instanceConfig.menu,"format"," | "+_common.component)})}));
+
+//# sourceMappingURL=configuration.min.js.map
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/amd/build/configuration.min.js.map b/lib/editor/tiny/plugins/noautolink/amd/build/configuration.min.js.map
new file mode 100644
index 00000000000..badaa6a316f
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/build/configuration.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"configuration.min.js","sources":["../src/configuration.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 * Tiny noautolink configuration.\n *\n * @module tiny_noautolink/configuration\n * @copyright 2023 Meirza \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\nimport {component as buttonName} from './common';\nimport {addToolbarButton, addMenubarItem} from 'editor_tiny/utils';\n\nexport const configure = (instanceConfig) => {\n // Update the instance configuration to add the noautolink menu option to the menus and toolbars.\n return {\n toolbar: addToolbarButton(instanceConfig.toolbar, 'content', buttonName),\n menu: addMenubarItem(instanceConfig.menu, 'format', ' | ' + buttonName),\n };\n};\n"],"names":["instanceConfig","toolbar","buttonName","menu"],"mappings":"0NA2B0BA,iBAEf,CACHC,SAAS,2BAAiBD,eAAeC,QAAS,UAAWC,mBAC7DC,MAAM,yBAAeH,eAAeG,KAAM,SAAU,MAAQD"}
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/amd/build/noautolink.min.js b/lib/editor/tiny/plugins/noautolink/amd/build/noautolink.min.js
new file mode 100644
index 00000000000..0dae29b7414
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/build/noautolink.min.js
@@ -0,0 +1,10 @@
+define("tiny_noautolink/noautolink",["exports","core/pending"],(function(_exports,_pending){var obj;
+/**
+ * Helper for Tiny noautolink plugin.
+ *
+ * @module tiny_noautolink/noautolink
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.toggleActiveState=_exports.handleAction=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};_exports.handleAction=(editor,messages)=>{const toggleState=isInAnchor(editor,editor.selection.getNode()),urlString=getSelectedContent(editor);toggleState||""===urlString?toggleState?unsetNoAutoLink(editor,messages,urlString):editor.notificationManager.open({text:messages.infoEmptySelection,type:"info",timeout:2e3}):setNoAutoLink(editor,messages,urlString)};const setNoAutoLink=(editor,messages,urlString)=>{if(isValidUrl(urlString)){const pendingPromise=new _pending.default("tiny_noautolink/setNoautolink");setNoautolinkOnSelection(editor,urlString).catch((error=>{editor.notificationManager.open({text:error,type:"error",timeout:2e3})})).finally((()=>{editor.notificationManager.open({text:messages.infoAddSuccess,type:"success",timeout:2e3}),pendingPromise.resolve()}))}else editor.notificationManager.open({text:messages.errorInvalidURL,type:"error",timeout:2e3})},unsetNoAutoLink=(editor,messages)=>{const nodeString=editor.selection.getNode().outerHTML.trim(),wrapper=document.createElement("div");wrapper.innerHTML=nodeString;if(wrapper.firstChild.classList.contains("nolink")){const pendingPromise=new _pending.default("tiny_noautolink/setNoautolink");unsetNoautolinkOnSelection(editor,nodeString).catch((error=>{editor.notificationManager.open({text:error,type:"error",timeout:2e3}),pendingPromise.reject(error)})).finally((()=>{editor.notificationManager.open({text:messages.infoRemoveSuccess,type:"success",timeout:2e3}),pendingPromise.resolve()}))}},getSelectedContent=editor=>{const selection=editor.selection;let content=selection.getContent({format:"text"}).trim();if(""==content){const range=selection.getRng();if(range.startContainer.nodeType===Node.TEXT_NODE){const textContent=range.startContainer.textContent,cursorOffset=range.startOffset;let wordStart=cursorOffset;for(;wordStart>0&&/\S/.test(textContent[wordStart-1]);)wordStart--;let wordEnd=cursorOffset;for(;wordEnd{const newContent="<".concat("span",' class="').concat("nolink",'">').concat(url,"").concat("span",">");editor.selection.setContent(newContent);const currentNode=editor.selection.getNode(),currentDOM=editor.dom.select("".concat("span",".").concat("nolink"),currentNode);currentDOM.forEach((function(value,index){value.outerHTML!=newContent||editor.selection.select(currentDOM[index])}))},unsetNoautolinkOnSelection=async(editor,url)=>{const regex=new RegExp("?".concat("span","[^>]*>"),"g");url=url.replace(regex,"");editor.dom.getParent(editor.selection.getNode(),"span").outerHTML=url},isValidUrl=urlString=>!!new RegExp("^((http|https):\\/\\/|www\\.)((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$","i").test(urlString),isInAnchor=(editor,selectedElm)=>null!==((editor,selectedElm)=>(selectedElm=selectedElm||editor.selection.getNode(),editor.dom.getParent(selectedElm,"".concat("span",".").concat("nolink"))))(editor,selectedElm);_exports.toggleActiveState=editor=>api=>{const updateState=()=>api.setActive(!editor.mode.isReadOnly()&&isInAnchor(editor,editor.selection.getNode()));return updateState(),((editor,toggler)=>(editor.on("NodeChange",toggler),()=>editor.off("NodeChange",toggler)))(editor,updateState)}}));
+
+//# sourceMappingURL=noautolink.min.js.map
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/amd/build/noautolink.min.js.map b/lib/editor/tiny/plugins/noautolink/amd/build/noautolink.min.js.map
new file mode 100644
index 00000000000..06a59883556
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/build/noautolink.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"noautolink.min.js","sources":["../src/noautolink.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 * Helper for Tiny noautolink plugin.\n *\n * @module tiny_noautolink/noautolink\n * @copyright 2023 Meirza \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\n\nconst noautolinkClassName = 'nolink';\nconst noautolinkTagHTML = 'span';\nconst notificationTimeout = 2000;\n\n/**\n * Handle action.\n *\n * @param {TinyMCE} editor\n * @param {object} messages\n */\nexport const handleAction = (editor, messages) => {\n const toggleState = isInAnchor(editor, editor.selection.getNode());\n const urlString = getSelectedContent(editor);\n if (!toggleState && urlString !== '') {\n setNoAutoLink(editor, messages, urlString);\n } else if (toggleState) {\n unsetNoAutoLink(editor, messages, urlString);\n } else {\n editor.notificationManager.open({text: messages.infoEmptySelection, type: 'info', timeout: notificationTimeout});\n }\n};\n\n/**\n * Display notification feedback when applying the noautolink to the selected text.\n *\n * @param {TinyMCE} editor\n * @param {object} messages\n * @param {String} urlString\n */\nconst setNoAutoLink = (editor, messages, urlString) => {\n // Check whether the string is a URL. Otherwise, show an error notification.\n if (isValidUrl(urlString)) {\n const pendingPromise = new Pending('tiny_noautolink/setNoautolink');\n // Applying the auto-link prevention.\n setNoautolinkOnSelection(editor, urlString)\n .catch(error => {\n editor.notificationManager.open({text: error, type: 'error', timeout: notificationTimeout});\n })\n .finally(() => {\n editor.notificationManager.open({text: messages.infoAddSuccess, type: 'success', timeout: notificationTimeout});\n pendingPromise.resolve();\n });\n } else {\n editor.notificationManager.open({text: messages.errorInvalidURL, type: 'error', timeout: notificationTimeout});\n }\n};\n\n/**\n * Display notification feedback when removing the noautolink to the selected text.\n *\n * @param {TinyMCE} editor\n * @param {object} messages\n */\nconst unsetNoAutoLink = (editor, messages) => {\n const nodeString = editor.selection.getNode().outerHTML.trim();\n // Convert HTML string to DOM element to get nolink class.\n const wrapper = document.createElement('div');\n wrapper.innerHTML = nodeString;\n const tempElement = wrapper.firstChild;\n if (tempElement.classList.contains('nolink')) {\n const pendingPromise = new Pending('tiny_noautolink/setNoautolink');\n // Removing the auto-link prevention.\n unsetNoautolinkOnSelection(editor, nodeString)\n .catch(error => {\n editor.notificationManager.open({text: error, type: 'error', timeout: notificationTimeout});\n pendingPromise.reject(error); // Handle the error as needed.\n })\n .finally(() => {\n editor.notificationManager.open({text: messages.infoRemoveSuccess, type: 'success', timeout: notificationTimeout});\n pendingPromise.resolve();\n });\n }\n};\n\n/**\n * Return the full string based on the position of the cursor within the string.\n *\n * @param {TinyMCE} editor\n * @returns {String}\n */\nconst getSelectedContent = (editor) => {\n const selection = editor.selection; // Get the selection object.\n let content = selection.getContent({format: 'text'}).trim();\n if (content == '') {\n const range = selection.getRng(); // Get the range object.\n\n // Check if the cursor is within a text node.\n if (range.startContainer.nodeType === Node.TEXT_NODE) {\n const textContent = range.startContainer.textContent;\n const cursorOffset = range.startOffset;\n\n // Find the word boundaries around the cursor.\n let wordStart = cursorOffset;\n while (wordStart > 0 && /\\S/.test(textContent[wordStart - 1])) {\n wordStart--;\n }\n\n let wordEnd = cursorOffset;\n while (wordEnd < textContent.length && /\\S/.test(textContent[wordEnd])) {\n wordEnd++;\n }\n\n // Set the selection range to the word.\n selection.setRng({\n startContainer: range.startContainer,\n startOffset: wordStart,\n endContainer: range.startContainer,\n endOffset: wordEnd,\n });\n content = selection.getContent({format: 'text'}).trim();\n }\n }\n return content;\n};\n\n/**\n * Wrap the selection with the nolink class.\n *\n * @param {TinyMCE} editor\n * @param {String} url URL the link will point to.\n */\nconst setNoautolinkOnSelection = async(editor, url) => {\n const newContent = `<${noautolinkTagHTML} class=\"${noautolinkClassName}\">${url}${noautolinkTagHTML}>`;\n editor.selection.setContent(newContent);\n\n // Select the new content.\n const currentNode = editor.selection.getNode();\n const currentDOM = editor.dom.select(`${noautolinkTagHTML}.${noautolinkClassName}`, currentNode);\n currentDOM.forEach(function(value, index) {\n if (value.outerHTML == newContent) {\n editor.selection.select(currentDOM[index]);\n return;\n }\n });\n};\n\n/**\n * Remove the nolink on the selection.\n *\n * @param {TinyMCE} editor\n * @param {String} url URL the link will point to.\n */\nconst unsetNoautolinkOnSelection = async(editor, url) => {\n const regex = new RegExp(`?${noautolinkTagHTML}[^>]*>`, \"g\");\n url = url.replace(regex, \"\");\n const currentSpan = editor.dom.getParent(editor.selection.getNode(), noautolinkTagHTML);\n currentSpan.outerHTML = url;\n};\n\n/**\n * Check if given string is a valid URL.\n *\n * @param {String} urlString URL the link will point to.\n * @returns {boolean} True is valid, otherwise false.\n */\nconst isValidUrl = urlString => {\n const urlPattern = new RegExp('^((http|https):\\\\/\\\\/|www\\\\.)' + // A URL must have one of these https/https/www.\n '((([a-z\\\\d]([a-z\\\\d-]*[a-z\\\\d])*)\\\\.)+[a-z]{2,}|' + // Validate domain name.\n '((\\\\d{1,3}\\\\.){3}\\\\d{1,3}))' + // Validate ip (v4) address.\n '(\\\\:\\\\d+)?(\\\\/[-a-z\\\\d%_.~+]*)*' + // Validate port and path.\n '(\\\\?[;&a-z\\\\d%_.~+=-]*)?' + // Validate query string.\n '(\\\\#[-a-z\\\\d_]*)?$', 'i'); // Validate fragment locator.\n\n return !!urlPattern.test(urlString);\n};\n\n/**\n * Get anchor element.\n *\n * @param {TinyMCE} editor\n * @param {Element} selectedElm\n * @returns {Element}\n */\nconst getAnchorElement = (editor, selectedElm) => {\n selectedElm = selectedElm || editor.selection.getNode();\n return editor.dom.getParent(selectedElm, `${noautolinkTagHTML}.${noautolinkClassName}`);\n};\n\n\n/**\n * Check the current selected element is an anchor or not.\n *\n * @param {TinyMCE} editor\n * @param {Element} selectedElm\n * @returns {boolean}\n */\nconst isInAnchor = (editor, selectedElm) => getAnchorElement(editor, selectedElm) !== null;\n\n/**\n * Change state of button.\n *\n * @param {TinyMCE} editor\n * @param {function()} toggler\n * @returns {function()}\n */\nconst toggleState = (editor, toggler) => {\n editor.on('NodeChange', toggler);\n return () => editor.off('NodeChange', toggler);\n};\n\n/**\n * Change the active state of button.\n *\n * @param {TinyMCE} editor\n * @returns {function(*): function(): *}\n */\nexport const toggleActiveState = (editor) => (api) => {\n const updateState = () => api.setActive(!editor.mode.isReadOnly() && isInAnchor(editor, editor.selection.getNode()));\n updateState();\n return toggleState(editor, updateState);\n};"],"names":["editor","messages","toggleState","isInAnchor","selection","getNode","urlString","getSelectedContent","unsetNoAutoLink","notificationManager","open","text","infoEmptySelection","type","timeout","setNoAutoLink","isValidUrl","pendingPromise","Pending","setNoautolinkOnSelection","catch","error","finally","infoAddSuccess","resolve","errorInvalidURL","nodeString","outerHTML","trim","wrapper","document","createElement","innerHTML","firstChild","classList","contains","unsetNoautolinkOnSelection","reject","infoRemoveSuccess","content","getContent","format","range","getRng","startContainer","nodeType","Node","TEXT_NODE","textContent","cursorOffset","startOffset","wordStart","test","wordEnd","length","setRng","endContainer","endOffset","async","url","newContent","setContent","currentNode","currentDOM","dom","select","forEach","value","index","regex","RegExp","replace","getParent","selectedElm","getAnchorElement","api","updateState","setActive","mode","isReadOnly","toggler","on","off"],"mappings":"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"}
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/amd/build/plugin.min.js b/lib/editor/tiny/plugins/noautolink/amd/build/plugin.min.js
new file mode 100644
index 00000000000..dc9b022f714
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/build/plugin.min.js
@@ -0,0 +1,10 @@
+define("tiny_noautolink/plugin",["exports","editor_tiny/loader","editor_tiny/utils","./common","./commands","./configuration"],(function(_exports,_loader,_utils,_common,Commands,Configuration){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}
+/**
+ * Tiny noautolink plugin.
+ *
+ * @module tiny_noautolink/plugin
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Commands=_interopRequireWildcard(Commands),Configuration=_interopRequireWildcard(Configuration);var _default=new Promise((async resolve=>{const[tinyMCE,setupCommands,pluginMetadata]=await Promise.all([(0,_loader.getTinyMCE)(),Commands.getSetup(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName)]);tinyMCE.PluginManager.add("".concat(_common.component,"/plugin"),(editor=>(setupCommands(editor),pluginMetadata))),resolve(["".concat(_common.component,"/plugin"),Configuration])}));return _exports.default=_default,_exports.default}));
+
+//# sourceMappingURL=plugin.min.js.map
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/amd/build/plugin.min.js.map b/lib/editor/tiny/plugins/noautolink/amd/build/plugin.min.js.map
new file mode 100644
index 00000000000..b890b8b2923
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/build/plugin.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"plugin.min.js","sources":["../src/plugin.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 * Tiny noautolink plugin.\n *\n * @module tiny_noautolink/plugin\n * @copyright 2023 Meirza \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\n\nimport {component, pluginName} from './common';\nimport * as Commands from './commands';\nimport * as Configuration from './configuration';\n\n// eslint-disable-next-line no-async-promise-executor\nexport default new Promise(async(resolve) => {\n const [\n tinyMCE,\n setupCommands,\n pluginMetadata,\n ] = await Promise.all([\n getTinyMCE(),\n Commands.getSetup(),\n getPluginMetadata(component, pluginName),\n ]);\n\n tinyMCE.PluginManager.add(`${component}/plugin`, (editor) => {\n // Setup the Commands (buttons, menu items, and so on).\n setupCommands(editor);\n\n return pluginMetadata;\n });\n\n // Resolve the noautolink Plugin and include configuration.\n resolve([`${component}/plugin`, Configuration]);\n});\n"],"names":["Promise","async","tinyMCE","setupCommands","pluginMetadata","all","Commands","getSetup","component","pluginName","PluginManager","add","editor","resolve","Configuration"],"mappings":";;;;;;;kMA+Be,IAAIA,SAAQC,MAAAA,gBAEnBC,QACAC,cACAC,sBACMJ,QAAQK,IAAI,EAClB,wBACAC,SAASC,YACT,4BAAkBC,kBAAWC,sBAGjCP,QAAQQ,cAAcC,cAAOH,8BAAqBI,SAE9CT,cAAcS,QAEPR,kBAIXS,QAAQ,WAAIL,6BAAoBM"}
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/amd/src/commands.js b/lib/editor/tiny/plugins/noautolink/amd/src/commands.js
new file mode 100644
index 00000000000..5912dea3da1
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/src/commands.js
@@ -0,0 +1,77 @@
+// 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 .
+
+import {getButtonImage} from 'editor_tiny/utils';
+import {get_string as getString} from 'core/str';
+import {component, buttonName, buttonIcon} from 'tiny_noautolink/common';
+import {handleAction, toggleActiveState} from 'tiny_noautolink/noautolink';
+
+/**
+ * Tiny noautolink commands.
+ *
+ * @module tiny_noautolink/commands
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+export const getSetup = async() => {
+ const [
+ buttonText,
+ errorInvalidURL,
+ infoEmptySelection,
+ infoAddSuccess,
+ infoRemoveSuccess,
+ buttonImage,
+ ] = await Promise.all([
+ getString('buttontitle', component),
+ getString('errorinvalidurl', component),
+ getString('infoemptyselection', component),
+ getString('infoaddsuccess', component),
+ getString('inforemovesuccess', component),
+ getButtonImage('icon', component),
+ ]);
+
+ return (editor) => {
+
+ const messages = {
+ errorInvalidURL: errorInvalidURL,
+ infoEmptySelection: infoEmptySelection,
+ infoAddSuccess: infoAddSuccess,
+ infoRemoveSuccess: infoRemoveSuccess
+ };
+
+ // Register the noautolink Icon.
+ editor.ui.registry.addIcon(buttonIcon, buttonImage.html);
+
+ // Register the noautolink button.
+ editor.ui.registry.addToggleButton(buttonName, {
+ icon: buttonIcon,
+ tooltip: buttonText,
+ onAction: () => {
+ handleAction(editor, messages);
+ },
+ onSetup: toggleActiveState(editor),
+ });
+
+ // Register the noautolink item.
+ editor.ui.registry.addMenuItem(buttonName, {
+ icon: buttonIcon,
+ text: buttonText,
+ onAction: () => {
+ handleAction(editor, messages);
+ },
+ });
+ };
+};
diff --git a/lib/editor/tiny/plugins/noautolink/amd/src/common.js b/lib/editor/tiny/plugins/noautolink/amd/src/common.js
new file mode 100644
index 00000000000..18e3bd8840f
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/src/common.js
@@ -0,0 +1,29 @@
+// 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 .
+
+/**
+ * Tiny noautolink common values.
+ *
+ * @module tiny_noautolink/common
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+export default {
+ pluginName: 'tiny_noautolink/plugin',
+ component: 'tiny_noautolink',
+ buttonName: 'tiny_noautolink',
+ buttonIcon: 'tiny_noautolink',
+};
diff --git a/lib/editor/tiny/plugins/noautolink/amd/src/configuration.js b/lib/editor/tiny/plugins/noautolink/amd/src/configuration.js
new file mode 100644
index 00000000000..c48fd2fb4f0
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/src/configuration.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 .
+
+/**
+ * Tiny noautolink configuration.
+ *
+ * @module tiny_noautolink/configuration
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+import {component as buttonName} from './common';
+import {addToolbarButton, addMenubarItem} from 'editor_tiny/utils';
+
+export const configure = (instanceConfig) => {
+ // Update the instance configuration to add the noautolink menu option to the menus and toolbars.
+ return {
+ toolbar: addToolbarButton(instanceConfig.toolbar, 'content', buttonName),
+ menu: addMenubarItem(instanceConfig.menu, 'format', ' | ' + buttonName),
+ };
+};
diff --git a/lib/editor/tiny/plugins/noautolink/amd/src/noautolink.js b/lib/editor/tiny/plugins/noautolink/amd/src/noautolink.js
new file mode 100644
index 00000000000..37e5814bf69
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/src/noautolink.js
@@ -0,0 +1,236 @@
+// 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 .
+
+/**
+ * Helper for Tiny noautolink plugin.
+ *
+ * @module tiny_noautolink/noautolink
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import Pending from 'core/pending';
+
+const noautolinkClassName = 'nolink';
+const noautolinkTagHTML = 'span';
+const notificationTimeout = 2000;
+
+/**
+ * Handle action.
+ *
+ * @param {TinyMCE} editor
+ * @param {object} messages
+ */
+export const handleAction = (editor, messages) => {
+ const toggleState = isInAnchor(editor, editor.selection.getNode());
+ const urlString = getSelectedContent(editor);
+ if (!toggleState && urlString !== '') {
+ setNoAutoLink(editor, messages, urlString);
+ } else if (toggleState) {
+ unsetNoAutoLink(editor, messages, urlString);
+ } else {
+ editor.notificationManager.open({text: messages.infoEmptySelection, type: 'info', timeout: notificationTimeout});
+ }
+};
+
+/**
+ * Display notification feedback when applying the noautolink to the selected text.
+ *
+ * @param {TinyMCE} editor
+ * @param {object} messages
+ * @param {String} urlString
+ */
+const setNoAutoLink = (editor, messages, urlString) => {
+ // Check whether the string is a URL. Otherwise, show an error notification.
+ if (isValidUrl(urlString)) {
+ const pendingPromise = new Pending('tiny_noautolink/setNoautolink');
+ // Applying the auto-link prevention.
+ setNoautolinkOnSelection(editor, urlString)
+ .catch(error => {
+ editor.notificationManager.open({text: error, type: 'error', timeout: notificationTimeout});
+ })
+ .finally(() => {
+ editor.notificationManager.open({text: messages.infoAddSuccess, type: 'success', timeout: notificationTimeout});
+ pendingPromise.resolve();
+ });
+ } else {
+ editor.notificationManager.open({text: messages.errorInvalidURL, type: 'error', timeout: notificationTimeout});
+ }
+};
+
+/**
+ * Display notification feedback when removing the noautolink to the selected text.
+ *
+ * @param {TinyMCE} editor
+ * @param {object} messages
+ */
+const unsetNoAutoLink = (editor, messages) => {
+ const nodeString = editor.selection.getNode().outerHTML.trim();
+ // Convert HTML string to DOM element to get nolink class.
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = nodeString;
+ const tempElement = wrapper.firstChild;
+ if (tempElement.classList.contains('nolink')) {
+ const pendingPromise = new Pending('tiny_noautolink/setNoautolink');
+ // Removing the auto-link prevention.
+ unsetNoautolinkOnSelection(editor, nodeString)
+ .catch(error => {
+ editor.notificationManager.open({text: error, type: 'error', timeout: notificationTimeout});
+ pendingPromise.reject(error); // Handle the error as needed.
+ })
+ .finally(() => {
+ editor.notificationManager.open({text: messages.infoRemoveSuccess, type: 'success', timeout: notificationTimeout});
+ pendingPromise.resolve();
+ });
+ }
+};
+
+/**
+ * Return the full string based on the position of the cursor within the string.
+ *
+ * @param {TinyMCE} editor
+ * @returns {String}
+ */
+const getSelectedContent = (editor) => {
+ const selection = editor.selection; // Get the selection object.
+ let content = selection.getContent({format: 'text'}).trim();
+ if (content == '') {
+ const range = selection.getRng(); // Get the range object.
+
+ // Check if the cursor is within a text node.
+ if (range.startContainer.nodeType === Node.TEXT_NODE) {
+ const textContent = range.startContainer.textContent;
+ const cursorOffset = range.startOffset;
+
+ // Find the word boundaries around the cursor.
+ let wordStart = cursorOffset;
+ while (wordStart > 0 && /\S/.test(textContent[wordStart - 1])) {
+ wordStart--;
+ }
+
+ let wordEnd = cursorOffset;
+ while (wordEnd < textContent.length && /\S/.test(textContent[wordEnd])) {
+ wordEnd++;
+ }
+
+ // Set the selection range to the word.
+ selection.setRng({
+ startContainer: range.startContainer,
+ startOffset: wordStart,
+ endContainer: range.startContainer,
+ endOffset: wordEnd,
+ });
+ content = selection.getContent({format: 'text'}).trim();
+ }
+ }
+ return content;
+};
+
+/**
+ * Wrap the selection with the nolink class.
+ *
+ * @param {TinyMCE} editor
+ * @param {String} url URL the link will point to.
+ */
+const setNoautolinkOnSelection = async(editor, url) => {
+ const newContent = `<${noautolinkTagHTML} class="${noautolinkClassName}">${url}${noautolinkTagHTML}>`;
+ editor.selection.setContent(newContent);
+
+ // Select the new content.
+ const currentNode = editor.selection.getNode();
+ const currentDOM = editor.dom.select(`${noautolinkTagHTML}.${noautolinkClassName}`, currentNode);
+ currentDOM.forEach(function(value, index) {
+ if (value.outerHTML == newContent) {
+ editor.selection.select(currentDOM[index]);
+ return;
+ }
+ });
+};
+
+/**
+ * Remove the nolink on the selection.
+ *
+ * @param {TinyMCE} editor
+ * @param {String} url URL the link will point to.
+ */
+const unsetNoautolinkOnSelection = async(editor, url) => {
+ const regex = new RegExp(`?${noautolinkTagHTML}[^>]*>`, "g");
+ url = url.replace(regex, "");
+ const currentSpan = editor.dom.getParent(editor.selection.getNode(), noautolinkTagHTML);
+ currentSpan.outerHTML = url;
+};
+
+/**
+ * Check if given string is a valid URL.
+ *
+ * @param {String} urlString URL the link will point to.
+ * @returns {boolean} True is valid, otherwise false.
+ */
+const isValidUrl = urlString => {
+ const urlPattern = new RegExp('^((http|https):\\/\\/|www\\.)' + // A URL must have one of these https/https/www.
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // Validate domain name.
+ '((\\d{1,3}\\.){3}\\d{1,3}))' + // Validate ip (v4) address.
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // Validate port and path.
+ '(\\?[;&a-z\\d%_.~+=-]*)?' + // Validate query string.
+ '(\\#[-a-z\\d_]*)?$', 'i'); // Validate fragment locator.
+
+ return !!urlPattern.test(urlString);
+};
+
+/**
+ * Get anchor element.
+ *
+ * @param {TinyMCE} editor
+ * @param {Element} selectedElm
+ * @returns {Element}
+ */
+const getAnchorElement = (editor, selectedElm) => {
+ selectedElm = selectedElm || editor.selection.getNode();
+ return editor.dom.getParent(selectedElm, `${noautolinkTagHTML}.${noautolinkClassName}`);
+};
+
+
+/**
+ * Check the current selected element is an anchor or not.
+ *
+ * @param {TinyMCE} editor
+ * @param {Element} selectedElm
+ * @returns {boolean}
+ */
+const isInAnchor = (editor, selectedElm) => getAnchorElement(editor, selectedElm) !== null;
+
+/**
+ * Change state of button.
+ *
+ * @param {TinyMCE} editor
+ * @param {function()} toggler
+ * @returns {function()}
+ */
+const toggleState = (editor, toggler) => {
+ editor.on('NodeChange', toggler);
+ return () => editor.off('NodeChange', toggler);
+};
+
+/**
+ * Change the active state of button.
+ *
+ * @param {TinyMCE} editor
+ * @returns {function(*): function(): *}
+ */
+export const toggleActiveState = (editor) => (api) => {
+ const updateState = () => api.setActive(!editor.mode.isReadOnly() && isInAnchor(editor, editor.selection.getNode()));
+ updateState();
+ return toggleState(editor, updateState);
+};
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/amd/src/plugin.js b/lib/editor/tiny/plugins/noautolink/amd/src/plugin.js
new file mode 100644
index 00000000000..bac130d94c0
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/amd/src/plugin.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 .
+
+/**
+ * Tiny noautolink plugin.
+ *
+ * @module tiny_noautolink/plugin
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import {getTinyMCE} from 'editor_tiny/loader';
+import {getPluginMetadata} from 'editor_tiny/utils';
+
+import {component, pluginName} from './common';
+import * as Commands from './commands';
+import * as Configuration from './configuration';
+
+// eslint-disable-next-line no-async-promise-executor
+export default new Promise(async(resolve) => {
+ const [
+ tinyMCE,
+ setupCommands,
+ pluginMetadata,
+ ] = await Promise.all([
+ getTinyMCE(),
+ Commands.getSetup(),
+ getPluginMetadata(component, pluginName),
+ ]);
+
+ tinyMCE.PluginManager.add(`${component}/plugin`, (editor) => {
+ // Setup the Commands (buttons, menu items, and so on).
+ setupCommands(editor);
+
+ return pluginMetadata;
+ });
+
+ // Resolve the noautolink Plugin and include configuration.
+ resolve([`${component}/plugin`, Configuration]);
+});
diff --git a/lib/editor/tiny/plugins/noautolink/classes/plugininfo.php b/lib/editor/tiny/plugins/noautolink/classes/plugininfo.php
new file mode 100644
index 00000000000..ce2fa5c21b9
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/classes/plugininfo.php
@@ -0,0 +1,53 @@
+.
+
+namespace tiny_noautolink;
+
+use editor_tiny\plugin;
+use editor_tiny\plugin_with_buttons;
+use editor_tiny\plugin_with_menuitems;
+
+/**
+ * Tiny noautolink plugin.
+ *
+ * @package tiny_noautolink
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class plugininfo extends plugin implements plugin_with_buttons, plugin_with_menuitems {
+
+ /**
+ * Get a list of the buttons provided by this plugin.
+ *
+ * @return string[]
+ */
+ public static function get_available_buttons(): array {
+ return [
+ 'tiny_noautolink/tiny_noautolink_link',
+ ];
+ }
+
+ /**
+ * Get a list of the menu items provided by this plugin.
+ *
+ * @return string[]
+ */
+ public static function get_available_menuitems(): array {
+ return [
+ 'tiny_noautolink/tiny_noautolink_link',
+ ];
+ }
+}
diff --git a/lib/editor/tiny/plugins/noautolink/classes/privacy/provider.php b/lib/editor/tiny/plugins/noautolink/classes/privacy/provider.php
new file mode 100644
index 00000000000..95ef63112b3
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/classes/privacy/provider.php
@@ -0,0 +1,37 @@
+.
+
+namespace tiny_noautolink\privacy;
+
+/**
+ * Privacy Subsystem for tiny_noautolink implementing null_provider.
+ *
+ * @package tiny_noautolink
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+ /**
+ * Get the language string identifier with the component's language
+ * file to explain why this plugin stores no data.
+ *
+ * @return string
+ */
+ public static function get_reason() : string {
+ return 'privacy:metadata';
+ }
+}
diff --git a/lib/editor/tiny/plugins/noautolink/db/install.php b/lib/editor/tiny/plugins/noautolink/db/install.php
new file mode 100644
index 00000000000..28152284281
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/db/install.php
@@ -0,0 +1,32 @@
+.
+
+/**
+ * Tiny noautolink plugin install code.
+ *
+ * @package tiny_noautolink
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Install the Tiny noautolink plugin in a disabled state.
+ */
+function xmldb_tiny_noautolink_install() {
+ $tinymanager = \core_plugin_manager::resolve_plugininfo_class('tiny');
+ // Disabled the plugin.
+ $tinymanager::enable_plugin('noautolink', 0);
+}
diff --git a/lib/editor/tiny/plugins/noautolink/lang/en/tiny_noautolink.php b/lib/editor/tiny/plugins/noautolink/lang/en/tiny_noautolink.php
new file mode 100644
index 00000000000..a0758250309
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/lang/en/tiny_noautolink.php
@@ -0,0 +1,31 @@
+.
+
+/**
+ * Strings for component 'tiny_noautolink', language 'en'.
+ *
+ * @package tiny_noautolink
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['buttontitle'] = 'No auto-link';
+$string['errorinvalidurl'] = 'The selected content is not a URL. Please try again.';
+$string['infoaddsuccess'] = 'Added the auto-link prevention.';
+$string['infoemptyselection'] = 'Select a URL and try again.';
+$string['inforemovesuccess'] = 'Removed the auto-link prevention.';
+$string['pluginname'] = 'Tiny no auto-link';
+$string['privacy:metadata'] = 'The tiny_noautolink plugin does not store any personal data.';
diff --git a/lib/editor/tiny/plugins/noautolink/pix/icon.svg b/lib/editor/tiny/plugins/noautolink/pix/icon.svg
new file mode 100644
index 00000000000..962eb5cc747
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/pix/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/noautolink/tests/behat/noautolink.feature b/lib/editor/tiny/plugins/noautolink/tests/behat/noautolink.feature
new file mode 100644
index 00000000000..b72297ea217
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/tests/behat/noautolink.feature
@@ -0,0 +1,21 @@
+@editor @editor_tiny @tiny_noautolink
+Feature: Tiny noautolink
+ To avoid auto-linking, users need to wrap the URL with the 'nolink' class.
+
+ @javascript
+ Scenario: Insert a link, add and remove the auto-link prevention
+ Given I log in as "admin"
+ And I navigate to "Plugins > Text editors > TinyMCE editor > General settings" in site administration
+ And I click on "Enable Tiny no auto-link" "link"
+ When I open my profile in edit mode
+ And I set the field "Description" to "
https://moodle.org
"
+
+ # Add auto-link prevention.
+ And I select the "p" element in position "0" of the "Description" TinyMCE editor
+ And I click on the "No auto-link" button for the "Description" TinyMCE editor
+ Then the field "Description" matches value "
https://moodle.org
"
+
+ # Remove auto-link prevention.
+ And I select the "span" element in position "0" of the "Description" TinyMCE editor
+ And I click on the "No auto-link" button for the "Description" TinyMCE editor
+ Then the field "Description" matches value "
https://moodle.org
"
diff --git a/lib/editor/tiny/plugins/noautolink/version.php b/lib/editor/tiny/plugins/noautolink/version.php
new file mode 100644
index 00000000000..bb254b6a0eb
--- /dev/null
+++ b/lib/editor/tiny/plugins/noautolink/version.php
@@ -0,0 +1,29 @@
+.
+
+/**
+ * Tiny noautolink plugin version details.
+ *
+ * @package tiny_noautolink
+ * @copyright 2023 Meirza
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2023081600; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2023081400; // Requires this Moodle version.
+$plugin->component = 'tiny_noautolink'; // Full name of the plugin (used for diagnostics).