diff --git a/lib/editor/tiny/plugins/premium/amd/build/common.min.js b/lib/editor/tiny/plugins/premium/amd/build/common.min.js
new file mode 100644
index 00000000000..959a0b8c8e4
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/amd/build/common.min.js
@@ -0,0 +1,3 @@
+define("tiny_premium/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={pluginName:"tiny_premium/plugin",component:"tiny_premium"},_exports.default}));
+
+//# sourceMappingURL=common.min.js.map
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/premium/amd/build/common.min.js.map b/lib/editor/tiny/plugins/premium/amd/build/common.min.js.map
new file mode 100644
index 00000000000..8ad877fb412
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/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 Premium common values.\n *\n * @module tiny_premium/common\n * @copyright 2023 David Woloszyn \n * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n pluginName: 'tiny_premium/plugin',\n component: 'tiny_premium',\n};\n"],"names":["pluginName","component"],"mappings":"qKAuBe,CACXA,WAAY,sBACZC,UAAW"}
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/premium/amd/build/configuration.min.js b/lib/editor/tiny/plugins/premium/amd/build/configuration.min.js
new file mode 100644
index 00000000000..621fad27a73
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/amd/build/configuration.min.js
@@ -0,0 +1,3 @@
+define("tiny_premium/configuration",["exports","editor_tiny/utils"],(function(_exports,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.configure=void 0;_exports.configure=instanceConfig=>{let plugins=instanceConfig.plugins,menu=instanceConfig.menu,toolbar=(toolbar=>(toolbar=(0,_utils.addToolbarSection)(toolbar,"premium_a","advanced",!0),(0,_utils.addToolbarSection)(toolbar,"premium_b","formatting",!0)))(instanceConfig.toolbar),contextmenu=instanceConfig.contextmenu;return plugins+=" advtable",menu=(0,_utils.addMenubarItem)(menu,"table","| advtablerownumbering","advtablesort"),plugins+=" editimage",toolbar=(0,_utils.addToolbarButton)(toolbar,"content","editimage","tiny_media_image"),instanceConfig.editimage_toolbar="rotateleft rotateright flipv fliph editimage",plugins+=" export",menu=(0,_utils.addMenubarItem)(menu,"tools","| export"),plugins+=" pageembed",toolbar=(0,_utils.addToolbarButton)(toolbar,"content","pageembed","tiny_media_video"),plugins+=" typography",toolbar=(0,_utils.addToolbarButton)(toolbar,"premium_b","typography"),plugins+=" casechange",toolbar=(0,_utils.addToolbarButton)(toolbar,"premium_a","casechange"),plugins+=" checklist",toolbar=(0,_utils.addToolbarButton)(toolbar,"lists","checklist"),plugins+=" tinymcespellchecker",menu=(0,_utils.addMenubarItem)(menu,"tools","spellcheckdialog","spellcheckerlanguage"),contextmenu=(0,_utils.addContextmenuItem)(contextmenu,"spellchecker"),toolbar=(0,_utils.addToolbarButton)(toolbar,"premium_a","spellcheckdialog"),plugins+=" autocorrect",menu=(0,_utils.addMenubarItem)(menu,"tools","| autocorrect capitalization","spellcheckdialog"),plugins+=" permanentpen",menu=(0,_utils.addMenubarItem)(menu,"format","| permanentpen configurepermanentpen"),toolbar=(0,_utils.addToolbarButton)(toolbar,"premium_a","permanentpen"),contextmenu=(0,_utils.addContextmenuItem)(contextmenu,"configurepermanentpen"),plugins+=" formatpainter",toolbar=(0,_utils.addToolbarButton)(toolbar,"premium_a","formatpainter"),plugins+=" linkchecker",contextmenu=(0,_utils.addContextmenuItem)(contextmenu,"linkchecker"),plugins+=" tableofcontents",toolbar=(0,_utils.addToolbarButton)(toolbar,"premium_a","tableofcontents"),plugins+=" footnotes",toolbar=(0,_utils.addToolbarButton)(toolbar,"premium_a","footnotes"),menu=(0,_utils.addMenubarItem)(menu,"insert","footnotes","tableofcontents"),plugins+=" powerpaste",{plugins:plugins,toolbar:toolbar,menu:menu,contextmenu:contextmenu}}}));
+
+//# sourceMappingURL=configuration.min.js.map
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/premium/amd/build/configuration.min.js.map b/lib/editor/tiny/plugins/premium/amd/build/configuration.min.js.map
new file mode 100644
index 00000000000..ca798b5a1b9
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/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 Premium configuration.\n *\n * @module tiny_premium/configuration\n * @copyright 2023 David Woloszyn \n * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {\n addToolbarButton,\n addMenubarItem,\n addToolbarSection,\n addContextmenuItem\n} from 'editor_tiny/utils';\n\nconst configureToolbar = (toolbar) => {\n // Add premium toolbar sections to house all the plugins with no natural home.\n toolbar = addToolbarSection(toolbar, 'premium_a', 'advanced', true);\n toolbar = addToolbarSection(toolbar, 'premium_b', 'formatting', true);\n return toolbar;\n};\n\nexport const configure = (instanceConfig) => {\n // There is some manipulating of the plugin menu, toolbar, context and quickbar items.\n // This was necessary to enhance user experience and closer align to the Tiny demo site.\n let plugins = instanceConfig.plugins;\n let menu = instanceConfig.menu;\n let toolbar = configureToolbar(instanceConfig.toolbar);\n let contextmenu = instanceConfig.contextmenu;\n let pluginsettings = {};\n\n // Advanced Table.\n plugins += ` advtable`;\n menu = addMenubarItem(menu, 'table', '| advtablerownumbering', 'advtablesort');\n\n // Enhanced Image Editing.\n plugins += ` editimage`;\n toolbar = addToolbarButton(toolbar, 'content', 'editimage', 'tiny_media_image');\n // Remove the duplicate image button from the quickbar toolbar by redefining the values without 'imageoptions'.\n // eslint-disable-next-line camelcase\n instanceConfig.editimage_toolbar = 'rotateleft rotateright flipv fliph editimage';\n\n // Export.\n plugins += ` export`;\n menu = addMenubarItem(menu, 'tools', '| export');\n\n // Page Embed.\n plugins += ` pageembed`;\n toolbar = addToolbarButton(toolbar, 'content', 'pageembed', 'tiny_media_video');\n\n // Advanced Typography.\n plugins += ` typography`;\n toolbar = addToolbarButton(toolbar, 'premium_b', 'typography');\n\n // Case Change.\n plugins += ` casechange`;\n toolbar = addToolbarButton(toolbar, 'premium_a', 'casechange');\n\n // Checklist.\n plugins += ` checklist`;\n toolbar = addToolbarButton(toolbar, 'lists', 'checklist');\n\n // Spell Checker Pro.\n plugins += ` tinymcespellchecker`;\n menu = addMenubarItem(menu, 'tools', 'spellcheckdialog', 'spellcheckerlanguage');\n contextmenu = addContextmenuItem(contextmenu, 'spellchecker');\n toolbar = addToolbarButton(toolbar, 'premium_a', 'spellcheckdialog');\n\n // Spelling Autocorrect.\n plugins += ` autocorrect`;\n menu = addMenubarItem(menu, 'tools', '| autocorrect capitalization', 'spellcheckdialog');\n\n // Permanent Pen.\n plugins += ` permanentpen`;\n menu = addMenubarItem(menu, 'format', '| permanentpen configurepermanentpen');\n toolbar = addToolbarButton(toolbar, 'premium_a', 'permanentpen');\n contextmenu = addContextmenuItem(contextmenu, 'configurepermanentpen');\n\n // Format Painter.\n plugins += ` formatpainter`;\n toolbar = addToolbarButton(toolbar, 'premium_a', 'formatpainter');\n\n // Link Checker.\n plugins += ` linkchecker`;\n contextmenu = addContextmenuItem(contextmenu, 'linkchecker');\n\n // Table of Contents.\n plugins += ` tableofcontents`;\n toolbar = addToolbarButton(toolbar, 'premium_a', 'tableofcontents');\n\n // Footnotes.\n plugins += ` footnotes`;\n toolbar = addToolbarButton(toolbar, 'premium_a', 'footnotes');\n menu = addMenubarItem(menu, 'insert', 'footnotes', 'tableofcontents');\n\n // Powerpaste.\n plugins += ` powerpaste`;\n\n return {\n plugins,\n toolbar,\n menu,\n contextmenu,\n ...pluginsettings\n };\n};\n"],"names":["instanceConfig","plugins","menu","toolbar","configureToolbar","contextmenu","editimage_toolbar"],"mappings":"oMAqC0BA,qBAGlBC,QAAUD,eAAeC,QACzBC,KAAOF,eAAeE,KACtBC,QAZkBA,CAAAA,UAEtBA,SAAU,4BAAkBA,QAAS,YAAa,YAAY,IACpD,4BAAkBA,QAAS,YAAa,cAAc,IASlDC,CAAiBJ,eAAeG,SAC1CE,YAAcL,eAAeK,mBAIjCJ,qBACAC,MAAO,yBAAeA,KAAM,QAAS,yBAA0B,gBAG/DD,sBACAE,SAAU,2BAAiBA,QAAS,UAAW,YAAa,oBAG5DH,eAAeM,kBAAoB,+CAGnCL,mBACAC,MAAO,yBAAeA,KAAM,QAAS,YAGrCD,sBACAE,SAAU,2BAAiBA,QAAS,UAAW,YAAa,oBAG5DF,uBACAE,SAAU,2BAAiBA,QAAS,YAAa,cAGjDF,uBACAE,SAAU,2BAAiBA,QAAS,YAAa,cAGjDF,sBACAE,SAAU,2BAAiBA,QAAS,QAAS,aAG7CF,gCACAC,MAAO,yBAAeA,KAAM,QAAS,mBAAoB,wBACzDG,aAAc,6BAAmBA,YAAa,gBAC9CF,SAAU,2BAAiBA,QAAS,YAAa,oBAGjDF,wBACAC,MAAO,yBAAeA,KAAM,QAAS,+BAAgC,oBAGrED,yBACAC,MAAO,yBAAeA,KAAM,SAAU,wCACtCC,SAAU,2BAAiBA,QAAS,YAAa,gBACjDE,aAAc,6BAAmBA,YAAa,yBAG9CJ,0BACAE,SAAU,2BAAiBA,QAAS,YAAa,iBAGjDF,wBACAI,aAAc,6BAAmBA,YAAa,eAG9CJ,4BACAE,SAAU,2BAAiBA,QAAS,YAAa,mBAGjDF,sBACAE,SAAU,2BAAiBA,QAAS,YAAa,aACjDD,MAAO,yBAAeA,KAAM,SAAU,YAAa,mBAGnDD,uBAEO,CACHA,QAAAA,QACAE,QAAAA,QACAD,KAAAA,KACAG,YAAAA"}
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/premium/amd/build/external.min.js b/lib/editor/tiny/plugins/premium/amd/build/external.min.js
new file mode 100644
index 00000000000..f23d5c14110
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/amd/build/external.min.js
@@ -0,0 +1,10 @@
+define("tiny_premium/external",["exports","core/ajax"],(function(_exports,_ajax){var obj;
+/**
+ * Helper to get external content for Tiny Premium plugin.
+ *
+ * @module tiny_premium/external
+ * @copyright 2023 David Woloszyn
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getApiKey=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.getApiKey=contextId=>{const request={methodname:"tiny_premium_get_api_key",args:{contextid:contextId}};return _ajax.default.call([request])[0]}}));
+
+//# sourceMappingURL=external.min.js.map
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/premium/amd/build/external.min.js.map b/lib/editor/tiny/plugins/premium/amd/build/external.min.js.map
new file mode 100644
index 00000000000..057c665ccb7
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/amd/build/external.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"external.min.js","sources":["../src/external.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 to get external content for Tiny Premium plugin.\n *\n * @module tiny_premium/external\n * @copyright 2023 David Woloszyn \n * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Due to the order items are initialise, options.js will not work in getting the API key.\n * This external method is used to get the API key in time.\n *\n * @param {Number} contextId The context id\n * @return {Promise}\n */\nexport const getApiKey = (contextId) => {\n const request = {\n methodname: 'tiny_premium_get_api_key',\n args: {\n contextid: contextId\n }\n };\n return Ajax.call([request])[0];\n};\n"],"names":["contextId","request","methodname","args","contextid","Ajax","call"],"mappings":";;;;;;;8JAgC0BA,kBAChBC,QAAU,CACZC,WAAY,2BACZC,KAAM,CACFC,UAAWJ,mBAGZK,cAAKC,KAAK,CAACL,UAAU"}
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/premium/amd/build/plugin.min.js b/lib/editor/tiny/plugins/premium/amd/build/plugin.min.js
new file mode 100644
index 00000000000..cb93b76838a
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/amd/build/plugin.min.js
@@ -0,0 +1,10 @@
+define("tiny_premium/plugin",["exports","editor_tiny/loader","editor_tiny/utils","tiny_premium/common","tiny_premium/configuration","tiny_premium/external","core/config"],(function(_exports,_loader,_utils,_common,Configuration,_external,_config){var obj;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)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Configuration=function(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]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}
+/**
+ * Tiny Premium plugin for Moodle.
+ *
+ * @module tiny_premium/plugin
+ * @copyright 2023 David Woloszyn
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */(Configuration);const currentContextId=(_config=(obj=_config)&&obj.__esModule?obj:{default:obj}).default.contextid;var _default=new Promise((async resolve=>{const[tinyMCE,pluginMetadata,externalData]=await Promise.all([(0,_loader.getTinyMCE)(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName),(0,_external.getApiKey)(currentContextId)]);tinyMCE.PluginManager.add("".concat(_common.component,"/plugin"),(()=>pluginMetadata)),await getTinyPremium(externalData.apikey),resolve(["".concat(_common.component,"/plugin"),Configuration])}));let tinyPremiumPromise;_exports.default=_default;const getTinyPremium=apikey=>tinyPremiumPromise||(tinyPremiumPromise=new Promise(((resolve,reject)=>{const head=document.querySelector("head"),script=document.createElement("script");script.dataset.tinymce="premium",script.src="https://cdn.tiny.cloud/1/".concat(apikey,"/tinymce/6/plugins.min.js"),script.referrerpolicy="origin",script.addEventListener("load",(()=>{resolve()}),!1),script.addEventListener("error",(err=>{reject(err)}),!1),head.append(script)})),tinyPremiumPromise);return _exports.default}));
+
+//# sourceMappingURL=plugin.min.js.map
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/premium/amd/build/plugin.min.js.map b/lib/editor/tiny/plugins/premium/amd/build/plugin.min.js.map
new file mode 100644
index 00000000000..f0fa0b24920
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/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 Premium plugin for Moodle.\n *\n * @module tiny_premium/plugin\n * @copyright 2023 David Woloszyn \n * @license https://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';\nimport {component, pluginName} from 'tiny_premium/common';\nimport * as Configuration from 'tiny_premium/configuration';\nimport {getApiKey} from 'tiny_premium/external';\nimport Config from 'core/config';\n\nconst currentContextId = Config.contextid;\n\n// eslint-disable-next-line no-async-promise-executor\nexport default new Promise(async(resolve) => {\n const [\n tinyMCE,\n pluginMetadata,\n externalData\n ] = await Promise.all([\n getTinyMCE(),\n getPluginMetadata(component, pluginName),\n getApiKey(currentContextId)\n ]);\n\n tinyMCE.PluginManager.add(`${component}/plugin`, () => {\n return pluginMetadata;\n });\n\n // Load the Tiny Premium script using the provided API key.\n await getTinyPremium(externalData.apikey);\n\n resolve([`${component}/plugin`, Configuration]);\n});\n\nlet tinyPremiumPromise;\n\n/**\n * Promise for Tiny Premium plugins script.\n *\n * @param {string} apikey\n * @return {Promise}\n */\nconst getTinyPremium = (apikey) => {\n if (tinyPremiumPromise) {\n return tinyPremiumPromise;\n }\n\n tinyPremiumPromise = new Promise((resolve, reject) => {\n const head = document.querySelector('head');\n const script = document.createElement('script');\n script.dataset.tinymce = 'premium';\n script.src = `https://cdn.tiny.cloud/1/${apikey}/tinymce/6/plugins.min.js`;\n script.referrerpolicy = \"origin\";\n\n script.addEventListener('load', () => {\n resolve();\n }, false);\n\n script.addEventListener('error', (err) => {\n reject(err);\n }, false);\n\n head.append(script);\n });\n\n return tinyPremiumPromise;\n};\n"],"names":["currentContextId","contextid","Promise","async","tinyMCE","pluginMetadata","externalData","all","component","pluginName","PluginManager","add","getTinyPremium","apikey","resolve","Configuration","tinyPremiumPromise","reject","head","document","querySelector","script","createElement","dataset","tinymce","src","referrerpolicy","addEventListener","err","append"],"mappings":";;;;;;;2BA8BMA,mFAA0BC,uBAGjB,IAAIC,SAAQC,MAAAA,gBAEnBC,QACAC,eACAC,oBACMJ,QAAQK,IAAI,EAClB,yBACA,4BAAkBC,kBAAWC,qBAC7B,uBAAUT,oBAGdI,QAAQM,cAAcC,cAAOH,8BAAoB,IACtCH,uBAILO,eAAeN,aAAaO,QAElCC,QAAQ,WAAIN,6BAAoBO,uBAGhCC,mDAQEJ,eAAkBC,QAChBG,qBAIJA,mBAAqB,IAAId,SAAQ,CAACY,QAASG,gBACjCC,KAAOC,SAASC,cAAc,QAC9BC,OAASF,SAASG,cAAc,UACtCD,OAAOE,QAAQC,QAAU,UACzBH,OAAOI,uCAAkCZ,oCACzCQ,OAAOK,eAAiB,SAExBL,OAAOM,iBAAiB,QAAQ,KAC5Bb,aACD,GAEHO,OAAOM,iBAAiB,SAAUC,MAC9BX,OAAOW,QACR,GAEHV,KAAKW,OAAOR,WAGTL"}
\ No newline at end of file
diff --git a/lib/editor/tiny/plugins/premium/amd/src/common.js b/lib/editor/tiny/plugins/premium/amd/src/common.js
new file mode 100644
index 00000000000..3ee35d20c5f
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/amd/src/common.js
@@ -0,0 +1,27 @@
+// 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 Premium common values.
+ *
+ * @module tiny_premium/common
+ * @copyright 2023 David Woloszyn
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+export default {
+ pluginName: 'tiny_premium/plugin',
+ component: 'tiny_premium',
+};
diff --git a/lib/editor/tiny/plugins/premium/amd/src/configuration.js b/lib/editor/tiny/plugins/premium/amd/src/configuration.js
new file mode 100644
index 00000000000..1b2cfa7e14c
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/amd/src/configuration.js
@@ -0,0 +1,121 @@
+// 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 Premium configuration.
+ *
+ * @module tiny_premium/configuration
+ * @copyright 2023 David Woloszyn
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import {
+ addToolbarButton,
+ addMenubarItem,
+ addToolbarSection,
+ addContextmenuItem
+} from 'editor_tiny/utils';
+
+const configureToolbar = (toolbar) => {
+ // Add premium toolbar sections to house all the plugins with no natural home.
+ toolbar = addToolbarSection(toolbar, 'premium_a', 'advanced', true);
+ toolbar = addToolbarSection(toolbar, 'premium_b', 'formatting', true);
+ return toolbar;
+};
+
+export const configure = (instanceConfig) => {
+ // There is some manipulating of the plugin menu, toolbar, context and quickbar items.
+ // This was necessary to enhance user experience and closer align to the Tiny demo site.
+ let plugins = instanceConfig.plugins;
+ let menu = instanceConfig.menu;
+ let toolbar = configureToolbar(instanceConfig.toolbar);
+ let contextmenu = instanceConfig.contextmenu;
+ let pluginsettings = {};
+
+ // Advanced Table.
+ plugins += ` advtable`;
+ menu = addMenubarItem(menu, 'table', '| advtablerownumbering', 'advtablesort');
+
+ // Enhanced Image Editing.
+ plugins += ` editimage`;
+ toolbar = addToolbarButton(toolbar, 'content', 'editimage', 'tiny_media_image');
+ // Remove the duplicate image button from the quickbar toolbar by redefining the values without 'imageoptions'.
+ // eslint-disable-next-line camelcase
+ instanceConfig.editimage_toolbar = 'rotateleft rotateright flipv fliph editimage';
+
+ // Export.
+ plugins += ` export`;
+ menu = addMenubarItem(menu, 'tools', '| export');
+
+ // Page Embed.
+ plugins += ` pageembed`;
+ toolbar = addToolbarButton(toolbar, 'content', 'pageembed', 'tiny_media_video');
+
+ // Advanced Typography.
+ plugins += ` typography`;
+ toolbar = addToolbarButton(toolbar, 'premium_b', 'typography');
+
+ // Case Change.
+ plugins += ` casechange`;
+ toolbar = addToolbarButton(toolbar, 'premium_a', 'casechange');
+
+ // Checklist.
+ plugins += ` checklist`;
+ toolbar = addToolbarButton(toolbar, 'lists', 'checklist');
+
+ // Spell Checker Pro.
+ plugins += ` tinymcespellchecker`;
+ menu = addMenubarItem(menu, 'tools', 'spellcheckdialog', 'spellcheckerlanguage');
+ contextmenu = addContextmenuItem(contextmenu, 'spellchecker');
+ toolbar = addToolbarButton(toolbar, 'premium_a', 'spellcheckdialog');
+
+ // Spelling Autocorrect.
+ plugins += ` autocorrect`;
+ menu = addMenubarItem(menu, 'tools', '| autocorrect capitalization', 'spellcheckdialog');
+
+ // Permanent Pen.
+ plugins += ` permanentpen`;
+ menu = addMenubarItem(menu, 'format', '| permanentpen configurepermanentpen');
+ toolbar = addToolbarButton(toolbar, 'premium_a', 'permanentpen');
+ contextmenu = addContextmenuItem(contextmenu, 'configurepermanentpen');
+
+ // Format Painter.
+ plugins += ` formatpainter`;
+ toolbar = addToolbarButton(toolbar, 'premium_a', 'formatpainter');
+
+ // Link Checker.
+ plugins += ` linkchecker`;
+ contextmenu = addContextmenuItem(contextmenu, 'linkchecker');
+
+ // Table of Contents.
+ plugins += ` tableofcontents`;
+ toolbar = addToolbarButton(toolbar, 'premium_a', 'tableofcontents');
+
+ // Footnotes.
+ plugins += ` footnotes`;
+ toolbar = addToolbarButton(toolbar, 'premium_a', 'footnotes');
+ menu = addMenubarItem(menu, 'insert', 'footnotes', 'tableofcontents');
+
+ // Powerpaste.
+ plugins += ` powerpaste`;
+
+ return {
+ plugins,
+ toolbar,
+ menu,
+ contextmenu,
+ ...pluginsettings
+ };
+};
diff --git a/lib/editor/tiny/plugins/premium/amd/src/external.js b/lib/editor/tiny/plugins/premium/amd/src/external.js
new file mode 100644
index 00000000000..da1946c3ce2
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/amd/src/external.js
@@ -0,0 +1,41 @@
+// 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 to get external content for Tiny Premium plugin.
+ *
+ * @module tiny_premium/external
+ * @copyright 2023 David Woloszyn
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import Ajax from 'core/ajax';
+
+/**
+ * Due to the order items are initialise, options.js will not work in getting the API key.
+ * This external method is used to get the API key in time.
+ *
+ * @param {Number} contextId The context id
+ * @return {Promise}
+ */
+export const getApiKey = (contextId) => {
+ const request = {
+ methodname: 'tiny_premium_get_api_key',
+ args: {
+ contextid: contextId
+ }
+ };
+ return Ajax.call([request])[0];
+};
diff --git a/lib/editor/tiny/plugins/premium/amd/src/plugin.js b/lib/editor/tiny/plugins/premium/amd/src/plugin.js
new file mode 100644
index 00000000000..db9da575e85
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/amd/src/plugin.js
@@ -0,0 +1,87 @@
+// 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 Premium plugin for Moodle.
+ *
+ * @module tiny_premium/plugin
+ * @copyright 2023 David Woloszyn
+ * @license https://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 'tiny_premium/common';
+import * as Configuration from 'tiny_premium/configuration';
+import {getApiKey} from 'tiny_premium/external';
+import Config from 'core/config';
+
+const currentContextId = Config.contextid;
+
+// eslint-disable-next-line no-async-promise-executor
+export default new Promise(async(resolve) => {
+ const [
+ tinyMCE,
+ pluginMetadata,
+ externalData
+ ] = await Promise.all([
+ getTinyMCE(),
+ getPluginMetadata(component, pluginName),
+ getApiKey(currentContextId)
+ ]);
+
+ tinyMCE.PluginManager.add(`${component}/plugin`, () => {
+ return pluginMetadata;
+ });
+
+ // Load the Tiny Premium script using the provided API key.
+ await getTinyPremium(externalData.apikey);
+
+ resolve([`${component}/plugin`, Configuration]);
+});
+
+let tinyPremiumPromise;
+
+/**
+ * Promise for Tiny Premium plugins script.
+ *
+ * @param {string} apikey
+ * @return {Promise}
+ */
+const getTinyPremium = (apikey) => {
+ if (tinyPremiumPromise) {
+ return tinyPremiumPromise;
+ }
+
+ tinyPremiumPromise = new Promise((resolve, reject) => {
+ const head = document.querySelector('head');
+ const script = document.createElement('script');
+ script.dataset.tinymce = 'premium';
+ script.src = `https://cdn.tiny.cloud/1/${apikey}/tinymce/6/plugins.min.js`;
+ script.referrerpolicy = "origin";
+
+ script.addEventListener('load', () => {
+ resolve();
+ }, false);
+
+ script.addEventListener('error', (err) => {
+ reject(err);
+ }, false);
+
+ head.append(script);
+ });
+
+ return tinyPremiumPromise;
+};
diff --git a/lib/editor/tiny/plugins/premium/classes/external/get_api_key.php b/lib/editor/tiny/plugins/premium/classes/external/get_api_key.php
new file mode 100644
index 00000000000..fc3e9a65987
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/classes/external/get_api_key.php
@@ -0,0 +1,78 @@
+.
+
+namespace tiny_premium\external;
+
+use context;
+use core_external\external_api;
+use core_external\external_function_parameters;
+use core_external\external_single_structure;
+use core_external\external_value;
+
+/**
+ * External API for Tiny Premium.
+ *
+ * @package tiny_premium
+ * @copyright 2023 David Woloszyn
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class get_api_key extends external_api {
+
+ /**
+ * Describes the parameters for Tiny Premium API key.
+ *
+ * @return external_function_parameters
+ * @since Moodle 4.3
+ */
+ public static function execute_parameters(): external_function_parameters {
+ return new external_function_parameters([
+ 'contextid' => new external_value(PARAM_INT, 'The current context ID.', VALUE_REQUIRED),
+ ]);
+ }
+
+ /**
+ * External function to get the Tiny Premium API key.
+ *
+ * @param int $contextid
+ * @return array
+ * @since Moodle 4.3
+ */
+ public static function execute(int $contextid): array {
+ [
+ 'contextid' => $contextid,
+ ] = self::validate_parameters(self::execute_parameters(), [
+ 'contextid' => $contextid,
+ ]);
+
+ $context = context::instance_by_id($contextid);
+ self::validate_context($context);
+ return [
+ 'apikey' => get_config('tiny_premium', 'apikey'),
+ ];
+ }
+
+ /**
+ * Describes the data returned from the external function.
+ *
+ * @return external_single_structure
+ * @since Moodle 4.3
+ */
+ public static function execute_returns(): external_single_structure {
+ return new external_single_structure([
+ 'apikey' => new external_value(PARAM_ALPHANUM, 'The API key for Tiny Premium'),
+ ]);
+ }
+}
diff --git a/lib/editor/tiny/plugins/premium/classes/plugininfo.php b/lib/editor/tiny/plugins/premium/classes/plugininfo.php
new file mode 100644
index 00000000000..09b11df295f
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/classes/plugininfo.php
@@ -0,0 +1,49 @@
+.
+
+namespace tiny_premium;
+
+use context;
+use editor_tiny\editor;
+use editor_tiny\plugin;
+
+/**
+ * Tiny Premium plugin.
+ *
+ * @package tiny_premium
+ * @copyright 2023 David Woloszyn
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class plugininfo extends plugin {
+
+ /**
+ * Determine if the plugin should be enabled by checking if the Tiny Premium API key is set.
+ *
+ * @param context $context The context that the editor is used within
+ * @param array $options The options passed in when requesting the editor
+ * @param array $fpoptions The filepicker options passed in when requesting the editor
+ * @param editor $editor The editor instance in which the plugin is initialised
+ * @return bool
+ */
+ public static function is_enabled(
+ context $context,
+ array $options,
+ array $fpoptions,
+ ?editor $editor = null
+ ): bool {
+ return get_config('tiny_premium', 'apikey') != false;
+ }
+}
diff --git a/lib/editor/tiny/plugins/premium/classes/privacy/provider.php b/lib/editor/tiny/plugins/premium/classes/privacy/provider.php
new file mode 100644
index 00000000000..984e7adc0c9
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/classes/privacy/provider.php
@@ -0,0 +1,37 @@
+.
+
+namespace tiny_premium\privacy;
+
+/**
+ * Privacy Subsystem implementation for the Premium plugin for TinyMCE.
+ *
+ * @package tiny_premium
+ * @copyright 2023 David Woloszyn
+ * @license https://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/premium/db/services.php b/lib/editor/tiny/plugins/premium/db/services.php
new file mode 100644
index 00000000000..b3e9e3ee62f
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/db/services.php
@@ -0,0 +1,36 @@
+.
+
+/**
+ * Tiny Premium external functions and service definitions.
+ *
+ * @package tiny_premium
+ * @copyright 2023 David Woloszyn
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$functions = [
+ 'tiny_premium_get_api_key' => [
+ 'classname' => 'tiny_premium\external\get_api_key',
+ 'methodname' => 'execute',
+ 'description' => 'Get the Tiny Premium API key from Moodle',
+ 'type' => 'read',
+ 'capabilities' => '',
+ 'ajax' => true,
+ ],
+];
diff --git a/lib/editor/tiny/plugins/premium/lang/en/tiny_premium.php b/lib/editor/tiny/plugins/premium/lang/en/tiny_premium.php
new file mode 100644
index 00000000000..45291f449fe
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/lang/en/tiny_premium.php
@@ -0,0 +1,31 @@
+.
+
+/**
+ * Strings for component 'tiny_premium', language 'en'.
+ *
+ * @package tiny_premium
+ * @category string
+ * @copyright 2023 David Woloszyn
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['pluginname'] = 'Tiny Premium';
+$string['apikey'] = 'API Key';
+$string['apikey_desc'] = 'Your Tiny Premium API Key requires a paid subscription. You can obtain your key by accessing your Tiny Cloud account page.';
+$string['privacy:metadata'] = 'The Tiny Premium plugin for TinyMCE does not store any personal data.';
diff --git a/lib/editor/tiny/plugins/premium/readme_moodle.txt b/lib/editor/tiny/plugins/premium/readme_moodle.txt
new file mode 100644
index 00000000000..d0afd906ebb
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/readme_moodle.txt
@@ -0,0 +1,12 @@
+Instructions for updating the Tiny Premium plugin for Moodle.
+
+A request to Tiny Cloud is made in the plugin.js file of this plugin.
+This request passes the Tiny Premium API key as part of a URL.
+The URL also contains the major version of Tiny and may need to be updated.
+
+The URL looks like this: https://cdn.tiny.cloud/1/YOUR_API_KEY/tinymce/6/plugins.min.js
+
+Notice that the version (6) is baked into the URL and may need revision.
+
+When upgrading, check Tiny Cloud's documentation regarding the correct API URL
+to use. Go to https://www.tiny.cloud/docs/tinymce
diff --git a/lib/editor/tiny/plugins/premium/settings.php b/lib/editor/tiny/plugins/premium/settings.php
new file mode 100644
index 00000000000..737ed7ceeaf
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/settings.php
@@ -0,0 +1,39 @@
+.
+
+/**
+ * Settings for the Tiny Premium plugin.
+ *
+ * @package tiny_premium
+ * @category admin
+ * @copyright 2023 David Woloszyn
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if ($hassiteconfig) {
+ // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
+ if ($ADMIN->fulltree) {
+ $setting = new admin_setting_configpasswordunmask(
+ 'tiny_premium/apikey',
+ get_string('apikey', 'tiny_premium'),
+ get_string('apikey_desc', 'tiny_premium'),
+ '',
+ );
+ $settings->add($setting);
+ }
+}
diff --git a/lib/editor/tiny/plugins/premium/version.php b/lib/editor/tiny/plugins/premium/version.php
new file mode 100644
index 00000000000..de44ec5e9ee
--- /dev/null
+++ b/lib/editor/tiny/plugins/premium/version.php
@@ -0,0 +1,29 @@
+.
+
+/**
+ * Plugin version and other meta-data are defined here.
+ *
+ * @package tiny_premium
+ * @copyright 2023 David Woloszyn
+ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2023082200;
+$plugin->requires = 2021051700;
+$plugin->component = 'tiny_premium';