diff --git a/administrator/components/com_content/config.xml b/administrator/components/com_content/config.xml index c037c6e9de367..b4f87091b8704 100644 --- a/administrator/components/com_content/config.xml +++ b/administrator/components/com_content/config.xml @@ -446,6 +446,17 @@ + + + + + JHIDE +
@@ -599,6 +600,18 @@ + + + + +
diff --git a/administrator/components/com_content/src/Model/ArticleModel.php b/administrator/components/com_content/src/Model/ArticleModel.php index 6474be7a2753f..9183d40ab80ba 100644 --- a/administrator/components/com_content/src/Model/ArticleModel.php +++ b/administrator/components/com_content/src/Model/ArticleModel.php @@ -16,6 +16,7 @@ use Joomla\CMS\Filter\InputFilter; use Joomla\CMS\Form\Form; use Joomla\CMS\Form\FormFactoryInterface; +use Joomla\CMS\Helper\ModuleHelper; use Joomla\CMS\Helper\TagsHelper; use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\LanguageHelper; @@ -446,6 +447,87 @@ public function getItem($pk = null) } } + // Expression to search for (positions) + $regex = '/{loadposition\s(.*?)}/i'; + + // Expression to search for (modules) + $regexmod = '/{loadmodule\s(.*?)}/i'; + + // Expression to search for (id) + $regexmodid = '/{loadmoduleid\s([1-9][0-9]*)}/i'; + + /** + * Find all instances of plugin and put in $matches for loadposition + * $matches[0] is full pattern match, $matches[1] is the position + */ + preg_match_all($regex, $item->articletext, $matches, PREG_SET_ORDER); + $item->importedPositions = []; + + // No matches, skip this + if ($matches) { + foreach ($matches as $match) { + $matcheslist = explode(',', $match[1]); + + $position = trim($matcheslist[0]); + $style = array_key_exists(1, $matcheslist) ? trim($matcheslist[1]) : 'none'; + + $item->importedPositions[] = array('name' => $position, 'style' => $style, 'editorText' => $match[0]); + } + } + + // Find all instances of plugin and put in $matchesmod for loadmodule + preg_match_all($regexmod, $item->articletext, $matchesmod, PREG_SET_ORDER); + + // If no matches, skip this + if ($matchesmod) { + foreach ($matchesmod as $matchmod) { + $matchesmodlist = explode(',', $matchmod[1]); + + $module = trim($matchesmodlist[0]); + $name = array_key_exists(1, $matchesmodlist) ? htmlspecialchars_decode(trim($matchesmodlist[1])) : null; + + $mod = ModuleHelper::getModule($module, $name); + + /** + * If the module without the mod_ isn't found, try it with mod_. + * This allows people to enter it either way in the content + */ + if (!isset($mod)) { + $mod = ModuleHelper::getModule('mod_' . $module, $name); + } + + if (isset($mod)) { + $mod->editorText = $matchmod[0]; + $item->importedModuleTypes[] = $mod; + } + } + } + + // Find all instances of loadmoduleid and store it in $matchesmodid + preg_match_all($regexmodid, $item->articletext, $matchesmodid, PREG_SET_ORDER); + $importedModules = []; + + // If no matches, skip this + if ($matchesmodid) { + foreach ($matchesmodid as $match) { + $importedModules[] = $match[1]; + } + + $db = $this->getDbo(); + $query = $db->getQuery(true) + ->select( + [ + $db->quoteName('id'), + $db->quoteName('title'), + $db->quoteName('published'), + ] + ) + ->from($db->quoteName('#__modules')); + $query->where($db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($importedModules))) . ')'); + + $item->importedModules = $db->setQuery($query)->loadObjectList(); + } + return $item; } diff --git a/administrator/components/com_content/tmpl/article/edit.php b/administrator/components/com_content/tmpl/article/edit.php index e794dfd8cc4bc..e1e3dd223b8e5 100644 --- a/administrator/components/com_content/tmpl/article/edit.php +++ b/administrator/components/com_content/tmpl/article/edit.php @@ -22,6 +22,29 @@ /** @var Joomla\CMS\WebAsset\WebAssetManager $wa */ $wa = $this->getDocument()->getWebAssetManager(); + +$wa->useScript('com_content.admin-article-edit'); + +// Set up the bootstrap modal that will be used for all module editors +echo HTMLHelper::_( + 'bootstrap.renderModal', + 'moduleEditModal', + array( + 'title' => Text::_('COM_CONTENT_HEADING_IMPORTED_MODULE_EDIT'), + 'backdrop' => 'static', + 'keyboard' => false, + 'closeButton' => false, + 'bodyHeight' => '70', + 'modalWidth' => '80', + 'footer' => '' + . '' + . '', + ) +); + $wa->getRegistry()->addExtensionRegistryFile('com_contenthistory'); $wa->useScript('keepalive') ->useScript('form.validate') @@ -110,6 +133,185 @@ + + get('show_imported_modules', 1) == 1 && (!empty($this->item->importedModules) || !empty($this->item->importedPositions))) : ?> + +
+ + + item->importedModules)) : ?> +

+ + + + + + + + + + + + + item->importedModules as $module) : ?> + + + + + + + + + +
+ +
+ + + + + + + + + +
+ escape($module->id); ?> + + escape($module->title); ?> + + published) : ?> + + + + + + + + + + + + +
+ + + item->importedModuleTypes)) : ?> +

+ + + + + + + + + + + + + item->importedModuleTypes as $module) : ?> + + + + + + + + + +
+ +
+ + + + + + + + + +
+ escape($module->id); ?> + + escape($module->module); ?> + + escape($module->title); ?> + + id)) : ?> + + + + +
+ + + item->importedPositions)) : ?> +

+ + + + + + + + + + + item->importedPositions as $position) : ?> + + + + + + + +
+ +
+ + + + + +
+ escape($position["name"]); ?> + + escape($position["style"]); ?> + + +
+ +
+ + + diff --git a/administrator/components/com_modules/src/View/Module/HtmlView.php b/administrator/components/com_modules/src/View/Module/HtmlView.php index e040c200670cd..0b1e1b3a68040 100644 --- a/administrator/components/com_modules/src/View/Module/HtmlView.php +++ b/administrator/components/com_modules/src/View/Module/HtmlView.php @@ -133,14 +133,17 @@ protected function addToolbar() if ($isNew && $canDo->get('core.create')) { $toolbar->apply('module.apply'); - $saveGroup = $toolbar->dropdownButton('save-group'); - - $saveGroup->configure( - function (Toolbar $childBar) { - $childBar->save('module.save'); - $childBar->save2new('module.save2new'); - } - ); + // Hide the Save and Close button for com_content Import Module Modal flow. + if (!Factory::getApplication()->input->cookie->get('com_modules_importOnSave')) { + $saveGroup = $toolbar->dropdownButton('save-group'); + + $saveGroup->configure( + function (Toolbar $childBar) { + $childBar->save('module.save'); + $childBar->save2new('module.save2new'); + } + ); + } $toolbar->cancel('module.cancel', 'JTOOLBAR_CANCEL'); } else { diff --git a/administrator/components/com_modules/tmpl/modules/modal.php b/administrator/components/com_modules/tmpl/modules/modal.php index 45111f6b0d5a1..780dd6831d29a 100644 --- a/administrator/components/com_modules/tmpl/modules/modal.php +++ b/administrator/components/com_modules/tmpl/modules/modal.php @@ -37,6 +37,10 @@ } ?>
+ + + +
@@ -46,8 +50,8 @@ diff --git a/administrator/language/en-GB/com_content.ini b/administrator/language/en-GB/com_content.ini index 0db92cc6123cb..e4cd5a3b32d36 100644 --- a/administrator/language/en-GB/com_content.ini +++ b/administrator/language/en-GB/com_content.ini @@ -32,6 +32,7 @@ COM_CONTENT_CREATE_ARTICLE_ERROR="When specific category is enabled, a category COM_CONTENT_CREATE_ARTICLE_REDIRECTMENU_DESC="Select the page the user will be redirected to after a successful article submission and after cancel (if not set differently below). The default is to redirect to the home page." COM_CONTENT_CREATE_ARTICLE_REDIRECTMENU_LABEL="Submission/Cancel Redirect" COM_CONTENT_DASHBOARD_TITLE="Content Dashboard" +COM_CONTENT_EDIT_MODULE="Edit" COM_CONTENT_EDIT_ARTICLE="Edit Article" COM_CONTENT_EDIT_CATEGORY="Edit Category" COM_CONTENT_EDITING_LAYOUT="Editing Layout" @@ -100,6 +101,7 @@ COM_CONTENT_FIELDS_ARTICLE_FIELD_ADD_TITLE="Articles: New Field" COM_CONTENT_FIELDS_ARTICLE_FIELD_EDIT_TITLE="Articles: Edit Field" COM_CONTENT_FIELDS_ARTICLE_FIELDS_TITLE="Articles: Fields" COM_CONTENT_FIELDS_TYPE_MODAL_ARTICLE="Article" +COM_CONTENT_FIELDSET_MODULES="Imported Modules" COM_CONTENT_FIELDSET_PUBLISHING="Publishing" COM_CONTENT_FIELDSET_RULES="Permissions" COM_CONTENT_FIELDSET_URLS_AND_IMAGES="Images and Links" @@ -115,9 +117,20 @@ COM_CONTENT_HEADING_DATE_CREATED="Date Created" COM_CONTENT_HEADING_DATE_MODIFIED="Date Modified" COM_CONTENT_HEADING_DATE_PUBLISH_DOWN="Finish Publishing" COM_CONTENT_HEADING_DATE_PUBLISH_UP="Start Publishing" +COM_CONTENT_HEADING_IMPORTED_MODULE_EDIT="Edit Module" +COM_CONTENT_HEADING_IMPORTED_MODULE_ID="ID" +COM_CONTENT_HEADING_IMPORTED_MODULE_TITLE="Title" +COM_CONTENT_HEADING_IMPORTED_MODULE_TYPE="Type" +COM_CONTENT_HEADING_IMPORTED_MODULE_TYPES="Modules Imported by Type" +COM_CONTENT_HEADING_IMPORTED_POSITION="Module Positions Imported" +COM_CONTENT_HEADING_IMPORTED_POSITION_NAME="Position" +COM_CONTENT_HEADING_IMPORTED_POSITION_STYLE="Style" +COM_CONTENT_HEADING_REMOVE="Remove from Article" COM_CONTENT_ID_LABEL="ID" COM_CONTENT_IMAGE_FULLTEXT_CLASS_LABEL="Full Text Image Class" COM_CONTENT_IMAGE_INTRO_CLASS_LABEL="Intro Image Class" +COM_CONTENT_IMPORTED_MODULES_TABLE_CAPTION="Table for Imported Modules" +COM_CONTENT_IMPORTED_POSITIONS_TABLE_CAPTION="Table for Imported Positions" COM_CONTENT_MODIFIED_ASC="Date Modified ascending" COM_CONTENT_MODIFIED_DESC="Date Modified descending" COM_CONTENT_MONTH="Month" @@ -160,7 +173,7 @@ COM_CONTENT_PUBLISH_DOWN_DESC="Finish Publishing descending" COM_CONTENT_PUBLISH_UP_ASC="Start Publishing ascending" COM_CONTENT_PUBLISH_UP_DESC="Start Publishing descending" COM_CONTENT_PUBLISHED="Published" -COM_CONTENT_RUN_TRANSITION="Run Transition" +COM_CONTENT_REMOVE_FROM_ARTICLE="Remove" COM_CONTENT_RUN_TRANSITIONS="Run Transitions" COM_CONTENT_SAVE_SUCCESS="Article saved." COM_CONTENT_SAVE_WARNING="Alias already existed so a number was added at the end. You can re-edit the article to customise the alias." @@ -173,6 +186,7 @@ COM_CONTENT_SHOW_ASSOCIATIONS_LABEL="Multilingual Associations" COM_CONTENT_SHOW_CONFIGURE_EDIT_LABEL="Edit Screen Options" COM_CONTENT_SHOW_IMAGES_URLS_BACK_LABEL="Administrator Images and Links" COM_CONTENT_SHOW_IMAGES_URLS_FRONT_LABEL="Frontend Images and Links" +COM_CONTENT_SHOW_IMPORTED_MODULES="Show Imported Modules" COM_CONTENT_SHOW_PERMISSIONS_LABEL="Article Permissions" COM_CONTENT_SHOW_PUBLISHING_OPTIONS_LABEL="Publishing Options" COM_CONTENT_SLIDER_EDITOR_CONFIG="Configure Edit Screen" diff --git a/build/media_source/com_content/joomla.asset.json b/build/media_source/com_content/joomla.asset.json index 741f9c58161fa..3b146aef4c9ec 100644 --- a/build/media_source/com_content/joomla.asset.json +++ b/build/media_source/com_content/joomla.asset.json @@ -5,6 +5,18 @@ "description": "Joomla CMS", "license": "GPL-2.0-or-later", "assets": [ + { + "name": "com_content.admin-article-edit", + "type": "script", + "uri": "com_content/admin-article-edit.min.js", + "dependencies": [ + "com_content.admin-article-edit.es5" + ], + "attributes": { + "type": "module", + "defer": true + } + }, { "name": "com_content.admin-article-pagebreak", "type": "script", diff --git a/build/media_source/com_content/js/admin-article-edit.es6.js b/build/media_source/com_content/js/admin-article-edit.es6.js new file mode 100644 index 0000000000000..bb76648ae8819 --- /dev/null +++ b/build/media_source/com_content/js/admin-article-edit.es6.js @@ -0,0 +1,96 @@ +/** + * @copyright (C) 2021 Open Source Matters, Inc. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ +(() => { + 'use strict'; + + /** + * Javascript to (1) Display the modal when user clicks on the + * module edit button. The modal is initialized by the id + * of the module found using data-module-id attribute of + * the button. (2) Remove imported modules by id or by + * position and (3) To delete the importOnSave cookie when + * the module modal is closed. + */ + + document.addEventListener('DOMContentLoaded', () => { + const baseLink = 'index.php?option=com_modules&client_id=0&task=module.edit&tmpl=component&view=module&layout=modal&id='; + const modalBtnElements = [].slice.call(document.getElementsByClassName('module-edit-link')); + const elements = [].slice.call(document.querySelectorAll('#moduleEditModal .modal-footer .btn')); + const removeModBtnElements = [].slice.call(document.getElementsByClassName('module-remove-link')); + const removePositionBtnElements = [].slice.call(document.getElementsByClassName('import-remove-link')); + const elModuleModal = document.getElementById('jform_articletext_editors-xtd_module_modal'); + + if (modalBtnElements.length) { + modalBtnElements.forEach((linkElement) => { + linkElement.addEventListener('click', (_ref) => { + const { target } = _ref; + const link = baseLink + target.getAttribute('data-module-id'); + const modal = document.getElementById('moduleEditModal'); + const body = modal.querySelector('.modal-body'); + const iFrame = document.createElement('iframe'); + iFrame.src = link; + iFrame.setAttribute('class', 'class="iframe jviewport-height70"'); + body.innerHTML = ''; + body.appendChild(iFrame); + modal.open(); + }); + }); + } + + if (elements.length) { + elements.forEach((element) => { + element.addEventListener('click', (_ref2) => { + const { target } = _ref2; + const dataTarget = target.getAttribute('data-bs-target'); + + if (dataTarget) { + const iframe = document.querySelector('#moduleEditModal iframe'); + const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; + iframeDocument.querySelector(dataTarget).click(); + } + }); + }); + } + + if (removeModBtnElements.length) { + removeModBtnElements.forEach((linkElement) => { + linkElement.addEventListener('click', (_ref) => { + const { target } = _ref; + const moduleId = target.getAttribute('data-module-id'); + let editorText = Joomla.editors.instances.jform_articletext.getValue(); + editorText = editorText.replace(`{loadmoduleid ${moduleId}}`, ''); + Joomla.editors.instances.jform_articletext.setValue(editorText); + document.querySelector('.button-apply.btn.btn-success').click(); + }); + }); + } + + if (removePositionBtnElements.length) { + removePositionBtnElements.forEach((linkElement) => { + linkElement.addEventListener('click', (_ref) => { + const { target } = _ref; + const importText = target.getAttribute('data-importText'); + let editorText = Joomla.editors.instances.jform_articletext.getValue(); + editorText = editorText.replace(importText, ''); + Joomla.editors.instances.jform_articletext.setValue(editorText); + document.querySelector('.button-apply.btn.btn-success').click(); + }); + }); + } + + // Clears the importOnSave cookie on closing the modal. + elModuleModal.addEventListener('hidden.bs.modal', () => { + const currentTime = new Date(); + currentTime.setTime(currentTime.getTime()); + document.cookie = `com_modules_importOnSave=0;expires=${currentTime.toUTCString()}`; + const iframeURL = document.querySelector('iframe[name="Module"]').contentWindow.location.href; + const indexOfId = iframeURL.indexOf('&id='); + if (indexOfId !== -1) { + const modid = iframeURL.slice(indexOfId + 4, iframeURL.indexOf('&', indexOfId + 4)); + Joomla.editors.instances.jform_articletext.replaceSelection(`{loadmoduleid ${modid}}`); + } + }); + }); +})(); diff --git a/build/media_source/com_modules/js/admin-modules-modal.es6.js b/build/media_source/com_modules/js/admin-modules-modal.es6.js index 04224ce983416..22346e537b331 100644 --- a/build/media_source/com_modules/js/admin-modules-modal.es6.js +++ b/build/media_source/com_modules/js/admin-modules-modal.es6.js @@ -47,4 +47,11 @@ } }); }); + + // Save a cookie that expires in one hour to notify that the module has to be imported if saved. + document.getElementById('importnewmodule').addEventListener('click', () => { + const expirationTime = new Date(); + expirationTime.setTime(expirationTime.getTime() + (60 * 60 * 1000)); + document.cookie = `com_modules_importOnSave=1;expires=${expirationTime.toUTCString()}`; + }); })();
, - , - + , +