From ad9e909927221302fbab3dd5b5584fc428909663 Mon Sep 17 00:00:00 2001 From: dGrammatiko Date: Thu, 13 Aug 2020 11:47:58 +0200 Subject: [PATCH 1/2] Init --- .../src/Controller/StylesController.php | 33 +++++++++ .../com_templates/src/Model/StylesModel.php | 4 + .../com_templates/src/Model/TemplateModel.php | 74 +++++++++++++------ .../src/Model/TemplatesModel.php | 4 + .../src/Service/HTML/Templates.php | 41 +++++++++- .../src/View/Styles/HtmlView.php | 9 ++- .../com_templates/tmpl/styles/default.php | 36 ++++++++- .../tmpl/template/default_folders.php | 13 ++-- .../tmpl/template/default_tree.php | 15 ++-- .../language/en-GB/com_templates.ini | 2 + .../js/admin-styles-default.es6.js | 53 +++++++++++++ libraries/src/Document/HtmlDocument.php | 2 +- .../src/Installer/Adapter/TemplateAdapter.php | 35 ++++++++- 13 files changed, 280 insertions(+), 41 deletions(-) create mode 100644 build/media_source/com_templates/js/admin-styles-default.es6.js diff --git a/administrator/components/com_templates/src/Controller/StylesController.php b/administrator/components/com_templates/src/Controller/StylesController.php index 42361a1cd223b..5d03585fc2e7d 100644 --- a/administrator/components/com_templates/src/Controller/StylesController.php +++ b/administrator/components/com_templates/src/Controller/StylesController.php @@ -23,6 +23,39 @@ */ class StylesController extends AdminController { + /** + * Method to create a child template. + * + * @return void + */ + public function createChild() + { + // Check for request forgeries + $this->checkToken(); + + $pks = $this->input->post->get('cid', array(), 'array'); + + try + { + if (empty($pks)) + { + throw new \Exception(Text::_('COM_TEMPLATES_NO_TEMPLATE_SELECTED')); + } + + $pks = ArrayHelper::toInteger($pks); + + $model = $this->getModel($name = 'Template', $prefix = 'Administrator', $config = array()); +// $model->duplicate($pks); + $this->setMessage(Text::_('COM_TEMPLATES_SUCCESS_DUPLICATED')); + } + catch (\Exception $e) + { + $this->app->enqueueMessage($e->getMessage(), 'error'); + } + + $this->setRedirect('index.php?option=com_templates&view=styles'); + } + /** * Method to clone and existing template style. * diff --git a/administrator/components/com_templates/src/Model/StylesModel.php b/administrator/components/com_templates/src/Model/StylesModel.php index f10071835f6fb..6b38895a92027 100644 --- a/administrator/components/com_templates/src/Model/StylesModel.php +++ b/administrator/components/com_templates/src/Model/StylesModel.php @@ -132,6 +132,8 @@ protected function getListQuery() $db->quoteName('a.title'), $db->quoteName('a.home'), $db->quoteName('a.client_id'), + $db->quoteName('a.inheritable'), + $db->quoteName('a.parent'), $db->quoteName('l.title', 'language_title'), $db->quoteName('l.image'), $db->quoteName('l.sef', 'language_sef'), @@ -157,6 +159,8 @@ protected function getListQuery() $db->quoteName('a.title'), $db->quoteName('a.home'), $db->quoteName('a.client_id'), + $db->quoteName('a.inheritable'), + $db->quoteName('a.parent'), $db->quoteName('l.title'), $db->quoteName('l.image'), $db->quoteName('l.sef'), diff --git a/administrator/components/com_templates/src/Model/TemplateModel.php b/administrator/components/com_templates/src/Model/TemplateModel.php index 02df31c05767a..f13746b8086ba 100644 --- a/administrator/components/com_templates/src/Model/TemplateModel.php +++ b/administrator/components/com_templates/src/Model/TemplateModel.php @@ -50,6 +50,22 @@ class TemplateModel extends FormModel */ protected $element = null; + /** + * The path to the template + * + * @var \stdClass + * @since 3.2 + */ + protected $elementMedia = null; + + /** + * Mode indicator + * + * @var boolean + * @since __DEPLOY_VERSION__ + */ + protected $supportsChildren = false; + /** * Internal method to get file properties. * @@ -384,24 +400,34 @@ public function getFiles() if ($template = $this->getTemplate()) { - $app = Factory::getApplication(); - $client = ApplicationHelper::getClientInfo($template->client_id); - $path = Path::clean($client->path . '/templates/' . $template->element . '/'); - $lang = Factory::getLanguage(); + $app = Factory::getApplication(); + $client = ApplicationHelper::getClientInfo($template->client_id); + $clientPath = ($client->id == 0) ? '' : 'administrator/'; + $this->element = Path::clean($client->path . '/templates/' . $template->element . '/'); + $this->elementMedia = Path::clean(JPATH_ROOT . '/media/templates/' . $clientPath . $template->element . '/');; + $lang = Factory::getLanguage(); // Load the core and/or local language file(s). $lang->load('tpl_' . $template->element, $client->path) || $lang->load('tpl_' . $template->element, $client->path . '/templates/' . $template->element); - $this->element = $path; - if (!is_writable($path)) + if (!is_writable($this->element)) { $app->enqueueMessage(Text::_('COM_TEMPLATES_DIRECTORY_NOT_WRITABLE'), 'error'); } - if (is_dir($path)) + if (is_dir($this->element)) { - $result = $this->getDirectoryTree($path); + $result = $this->getDirectoryTree($this->element); + + // New mode + if (is_dir($this->elementMedia)) + { + $resultTmp = $this->getDirectoryTree($this->elementMedia); + + $result = array_merge( + (array) $result, (array) $resultTmp); + } } else { @@ -439,6 +465,7 @@ public function getDirectoryTree($dir) if (is_dir($dir . $value)) { $relativePath = str_replace($this->element, '', $dir . $value); + $relativePath = str_replace($this->elementMedia, '', $relativePath); $result['/' . $relativePath] = $this->getDirectoryTree($dir . $value . '/'); } else @@ -929,20 +956,12 @@ public function &getSource() if ($this->template) { - $input = Factory::getApplication()->input; - $fileName = base64_decode($input->get('file')); - $client = ApplicationHelper::getClientInfo($this->template->client_id); + $input = Factory::getApplication()->input; + $fileName = base64_decode($input->get('file')); + $client = ApplicationHelper::getClientInfo($this->template->client_id); + $clientPath = ($client->id == 0) ? '' : 'administrator/'; - try - { - $filePath = Path::check($client->path . '/templates/' . $this->template->element . '/' . $fileName); - } - catch (\Exception $e) - { - $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_FOUND'), 'error'); - - return; - } + $filePath = Path::check($client->path . '/templates/' . $this->template->element . $fileName); if (file_exists($filePath)) { @@ -957,6 +976,19 @@ public function &getSource() $item->core = file_get_contents($coreFile); } } + elseif (file_exists($filePathNew = Path::check($fileName))) + { + $item->extension_id = $this->getState('extension.id'); + $item->filename = Path::clean($fileName); + $item->source = file_get_contents($filePathNew); + $item->filePath = Path::clean($filePathNew); + + if ($coreFile = $this->getCoreFile($fileName, $this->template->client_id)) + { + $item->coreFile = $coreFile; + $item->core = file_get_contents($coreFile); + } + } else { $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_FOUND'), 'error'); diff --git a/administrator/components/com_templates/src/Model/TemplatesModel.php b/administrator/components/com_templates/src/Model/TemplatesModel.php index 2986f71318e15..0a491098e3bcd 100644 --- a/administrator/components/com_templates/src/Model/TemplatesModel.php +++ b/administrator/components/com_templates/src/Model/TemplatesModel.php @@ -142,6 +142,10 @@ protected function getListQuery() ->where($db->quoteName('a.client_id') . ' = :clientid') ->where($db->quoteName('a.enabled') . ' = 1') ->where($db->quoteName('a.type') . ' = ' . $db->quote('template')) + + // Exclude children templates + ->join('INNER', '#__template_styles AS b', $db->quoteName('a.element') . ' = b.template AND ' . $db->quoteName('b.parent') . ' = ""') + ->bind(':clientid', $clientId, ParameterType::INTEGER); // Filter by search in title. diff --git a/administrator/components/com_templates/src/Service/HTML/Templates.php b/administrator/components/com_templates/src/Service/HTML/Templates.php index 9f18f7d9e8a86..587a6fc51e829 100644 --- a/administrator/components/com_templates/src/Service/HTML/Templates.php +++ b/administrator/components/com_templates/src/Service/HTML/Templates.php @@ -39,11 +39,11 @@ public function thumb($template, $clientId = 0) $basePath = $client->path . '/templates/' . $template; $thumb = $basePath . '/template_thumbnail.png'; $preview = $basePath . '/template_preview.png'; + $clientPath = ($clientId == 0) ? '' : 'administrator/'; $html = ''; if (file_exists($thumb)) { - $clientPath = ($clientId == 0) ? '' : 'administrator/'; $thumb = $clientPath . 'templates/' . $template . '/template_thumbnail.png'; $html = HTMLHelper::_('image', $thumb, Text::_('COM_TEMPLATES_PREVIEW')); @@ -53,6 +53,19 @@ public function thumb($template, $clientId = 0) } } + // Alter the paths for the new mode + $basePath = JPATH_ROOT . '/media/templates/' . $clientPath . $template; + $thumb = $basePath . '/template_thumbnail.png'; + + if (file_exists($thumb)) { + $html = HTMLHelper::_('image', 'media/templates/' . $clientPath . $template . '/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'), [], false); + + if (file_exists($basePath . '/template_preview.png')) + { + $html = ''; + } + } + return $html; } @@ -70,6 +83,7 @@ public function thumbModal($template, $clientId = 0) { $client = ApplicationHelper::getClientInfo($clientId); $basePath = $client->path . '/templates/' . $template; + $clientPath = ($clientId == 0) ? '' : 'administrator/'; $baseUrl = ($clientId == 0) ? Uri::root(true) : Uri::root(true) . '/administrator'; $thumb = $basePath . '/template_thumbnail.png'; $preview = $basePath . '/template_preview.png'; @@ -94,6 +108,31 @@ public function thumbModal($template, $clientId = 0) ); } + // Alter the paths for the new mode + $basePath = JPATH_ROOT . '/media/templates/' . $clientPath . $template; + $baseUrl = Uri::root(true); + $thumb = $basePath . '/template_thumbnail.png'; + $preview = $basePath . '/template_preview.png'; + + if (file_exists($thumb) && file_exists($preview)) + { + $preview = '/media/templates/' . $clientPath . $template . '/template_preview.png'; + $footer = ''; + + $html .= HTMLHelper::_( + 'bootstrap.renderModal', + $template . '-Modal', + array( + 'title' => Text::sprintf('COM_TEMPLATES_SCREENSHOT', ucfirst($template)), + 'height' => '500px', + 'width' => '800px', + 'footer' => $footer, + ), + $body = '
' . $template . '
' + ); + } + return $html; } } diff --git a/administrator/components/com_templates/src/View/Styles/HtmlView.php b/administrator/components/com_templates/src/View/Styles/HtmlView.php index d43ddcb315dca..daab3ba57231c 100644 --- a/administrator/components/com_templates/src/View/Styles/HtmlView.php +++ b/administrator/components/com_templates/src/View/Styles/HtmlView.php @@ -16,6 +16,8 @@ use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\View\GenericDataException; use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; +use Joomla\CMS\Session\Session; +use Joomla\CMS\Toolbar\Toolbar; use Joomla\CMS\Toolbar\ToolbarHelper; /** @@ -115,6 +117,7 @@ public function display($tpl = null) protected function addToolbar() { $canDo = ContentHelper::getActions('com_templates'); + $bar = Toolbar::getInstance('toolbar'); // Set the title. if ((int) $this->get('State')->get('client_id') === 1) @@ -128,12 +131,16 @@ protected function addToolbar() if ($canDo->get('core.edit.state')) { - ToolbarHelper::makeDefault('styles.setDefault', 'COM_TEMPLATES_TOOLBAR_SET_HOME'); + $bar->appendButton('Custom', '', 'COM_TEMPLATES_TOOLBAR_SET_HOME', 'styleSetDefault'); ToolbarHelper::divider(); } if ($canDo->get('core.create')) { + $bar->appendButton('Custom', '', 'JTOOLBAR_CREATE_CHILD', 'createChild'); + ToolbarHelper::divider(); + $bar->appendButton('Custom', '', 'JTOOLBAR_OVERRIDE_CHILD', 'overrideChild'); + ToolbarHelper::divider(); ToolbarHelper::custom('styles.duplicate', 'copy.png', 'copy_f2.png', 'JTOOLBAR_DUPLICATE', true); ToolbarHelper::divider(); } diff --git a/administrator/components/com_templates/tmpl/styles/default.php b/administrator/components/com_templates/tmpl/styles/default.php index 28da75c06320f..04ac07b84e0ce 100644 --- a/administrator/components/com_templates/tmpl/styles/default.php +++ b/administrator/components/com_templates/tmpl/styles/default.php @@ -22,6 +22,29 @@ $clientId = (int) $this->state->get('client_id', 0); $listOrder = $this->escape($this->state->get('list.ordering')); $listDirn = $this->escape($this->state->get('list.direction')); + +/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */ +$wa = $this->document->getWebAssetManager(); +$wa->addInlineStyle( +<< span { +-webkit-margin-end: .5rem; +margin-inline-end: .5rem; +} +CSS +); + +$wa->registerAndUseScript('com_templates.styles_default_esm', 'com_templates/admin-styles-default.es6.js', ['version' => 'auto', 'relative' => true], ['type' => 'module', 'defer' => true]); +$wa->registerAndUseScript('com_templates.styles_default', 'com_templates/admin-styles-default.js', ['version' => 'auto', 'relative' => true], ['nomodule' => '', 'defer' => true]); ?>
@@ -38,7 +61,7 @@ - + @@ -67,10 +90,19 @@ $canCreate = $user->authorise('core.create', 'com_templates'); $canEdit = $user->authorise('core.edit', 'com_templates'); $canChange = $user->authorise('core.edit.state', 'com_templates'); + $isParent = $item->inheritable ? 'true' : 'false'; + $canEdit = $canChange ? 'true' : 'false'; + $isHome = $item->home == '0' ? 'false' : 'true'; + $isChild = !empty($item->parent) ? 'true' : 'false'; ?> - id, false, 'cid', 'cb', $item->title); ?> + ' . Text::_('JSELECT') + . ' ' . htmlspecialchars($item->title, ENT_COMPAT, 'UTF-8') . '' + . ''; + ?> diff --git a/administrator/components/com_templates/tmpl/template/default_folders.php b/administrator/components/com_templates/tmpl/template/default_folders.php index e434ff043417a..32022304e6eaf 100644 --- a/administrator/components/com_templates/tmpl/template/default_folders.php +++ b/administrator/components/com_templates/tmpl/template/default_folders.php @@ -10,16 +10,17 @@ defined('_JEXEC') or die; ksort($this->files, SORT_STRING); ?> -
    files as $key => $value) : ?>
  • - - - escape(end($explodeArray)); ?> - - folderTree($value); ?> +
    + + + escape(end($explodeArray)); ?> + + folderTree($value); ?> +
  • diff --git a/administrator/components/com_templates/tmpl/template/default_tree.php b/administrator/components/com_templates/tmpl/template/default_tree.php index 7324ee771caa3..b2c0d47162d31 100644 --- a/administrator/components/com_templates/tmpl/template/default_tree.php +++ b/administrator/components/com_templates/tmpl/template/default_tree.php @@ -12,6 +12,11 @@ use Joomla\CMS\Router\Route; ksort($this->files, SORT_NATURAL); + +/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */ +$wa = $this->document->getWebAssetManager(); +$wa->addInlineStyle('.CodeMirror { height: 100%}'); + ?>
      @@ -50,15 +55,15 @@ ?>
    • - -  escape(end($explodeArray)); ?> - - directoryTree($value); ?> +
      +  escape(end($explodeArray)); ?> + directoryTree($value); ?> +
    • - id . '&file=' . $value->id); ?>'> + id . '&file=' . $value->id); ?>' style="background:transparent; border:0; color: var(--atum-link-color)">  escape($value->name); ?>
    • diff --git a/administrator/language/en-GB/com_templates.ini b/administrator/language/en-GB/com_templates.ini index c5fc3bc9c30eb..f845db0a5beaa 100644 --- a/administrator/language/en-GB/com_templates.ini +++ b/administrator/language/en-GB/com_templates.ini @@ -37,6 +37,8 @@ COM_TEMPLATES_CONFIG_SOURCE_LABEL="Valid Source Formats" COM_TEMPLATES_CONFIG_UPLOAD_LABEL="Max. Upload Size (MB)" COM_TEMPLATES_CONFIGURATION="Template: Options" COM_TEMPLATES_COPY_SUCCESS="New template called %s was installed." +JTOOLBAR_CREATE_CHILD="Duplicate as child" +JTOOLBAR_OVERRIDE_CHILD="Manage overrides" COM_TEMPLATES_CROP_AREA_ERROR="Crop area not selected." COM_TEMPLATES_DIRECTORY_NOT_WRITABLE="The template folder is not writable. Some features may not work." COM_TEMPLATES_ERR_XML="Template XML data not available" diff --git a/build/media_source/com_templates/js/admin-styles-default.es6.js b/build/media_source/com_templates/js/admin-styles-default.es6.js new file mode 100644 index 0000000000000..766660d2a974f --- /dev/null +++ b/build/media_source/com_templates/js/admin-styles-default.es6.js @@ -0,0 +1,53 @@ +/** + * @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ +(() => { + 'use strict'; + + // Shortcuts + const $ = document.querySelector.bind(document); + const $$ = document.querySelectorAll.bind(document); + + // Get a reference of all the elements needed + const defaultButton = $('#style-default'); + const createChildButton = $('#create-child'); + const overrideChildButton = $('#override-child'); + + const duplicateButton = $('#toolbar-copy > button'); + const deleteButton = $('#toolbar-delete > button'); + const stylesElements = [].slice.call($$('input[name="cid[]"]')); + + if (!defaultButton || !createChildButton || !overrideChildButton || !duplicateButton || !deleteButton || stylesElements.count) { + throw new Error('Some elements are missing, bailing out'); + } + + const manageButtons = (event) => { + const element = event.currentTarget; + + // Enable Default button + if (element.dataset.isHome !== 'true') { + defaultButton.removeAttribute('disabled'); + } else { + defaultButton.setAttribute('disabled', ''); + } + + // Enable Duplicate as chid button + if (element.dataset.isParent === 'true') { + createChildButton.removeAttribute('disabled'); + } else { + createChildButton.setAttribute('disabled', ''); + } + + // Enable Manage child overrides button + if (element.dataset.isChild === 'true') { + overrideChildButton.removeAttribute('disabled'); + } else { + overrideChildButton.setAttribute('disabled', ''); + } + }; + + stylesElements.forEach((element) => { + element.addEventListener('click', manageButtons); + }); +})(); diff --git a/libraries/src/Document/HtmlDocument.php b/libraries/src/Document/HtmlDocument.php index 5e50d092cb825..a013835bbea1b 100644 --- a/libraries/src/Document/HtmlDocument.php +++ b/libraries/src/Document/HtmlDocument.php @@ -806,7 +806,7 @@ protected function _fetchTemplate($params = array()) $filter = InputFilter::getInstance(); $template = $filter->clean($params['template'], 'cmd'); $file = $filter->clean($params['file'], 'cmd'); - $inherits = $params['templateInherits']; + $inherits = $params['templateInherits'] ?? ''; $baseDir = $directory . '/' . $template; if (!empty($inherits) diff --git a/libraries/src/Installer/Adapter/TemplateAdapter.php b/libraries/src/Installer/Adapter/TemplateAdapter.php index 0c2e22389073e..c1bd38f2a614b 100644 --- a/libraries/src/Installer/Adapter/TemplateAdapter.php +++ b/libraries/src/Installer/Adapter/TemplateAdapter.php @@ -323,8 +323,8 @@ protected function parseQueries() $db->quoteName('home'), $db->quoteName('title'), $db->quoteName('params'), + $db->quoteName('inheritable'), $db->quoteName('parent'), - $db->quoteName('inherits'), ]; $values = $query->bindArray( @@ -471,24 +471,51 @@ protected function setupUninstall() // Deny remove default template $db = $this->parent->getDbo(); $query = $db->getQuery(true) - ->select('COUNT(*)') + ->select('*') ->from($db->quoteName('#__template_styles')) ->where( [ - $db->quoteName('home') . ' = ' . $db->quote('1'), $db->quoteName('template') . ' = :template', $db->quoteName('client_id') . ' = :client_id', ] ) + ->orWhere( + [ + $db->quoteName('parent') . ' = :template', + $db->quoteName('client_id') . ' = :client_id', + ] + ) ->bind(':template', $name) ->bind(':client_id', $clientId); $db->setQuery($query); + $results = $db->loadObjectList(); + + $isHome = false; + $hasChild = false; + + foreach ($results as $k => $v) + { + if ((int) $v->home === 1) + { + $isHome = true; + } + + if ($v->parent !== '') + { + $hasChild = true; + } + } - if ($db->loadResult() != 0) + if ($isHome) { throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_DEFAULT')); } + if ($hasChild) + { + throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_PARENT')); + } + // Get the template root path $client = ApplicationHelper::getClientInfo($clientId); From fc65b9febe87985a83e50133d5db4f01fce43d6c Mon Sep 17 00:00:00 2001 From: dGrammatiko Date: Thu, 13 Aug 2020 17:45:03 +0200 Subject: [PATCH 2/2] Update administrator/language/en-GB/com_templates.ini Co-authored-by: Brian Teeman --- administrator/language/en-GB/com_templates.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/administrator/language/en-GB/com_templates.ini b/administrator/language/en-GB/com_templates.ini index f845db0a5beaa..57a4bfe800e35 100644 --- a/administrator/language/en-GB/com_templates.ini +++ b/administrator/language/en-GB/com_templates.ini @@ -37,8 +37,8 @@ COM_TEMPLATES_CONFIG_SOURCE_LABEL="Valid Source Formats" COM_TEMPLATES_CONFIG_UPLOAD_LABEL="Max. Upload Size (MB)" COM_TEMPLATES_CONFIGURATION="Template: Options" COM_TEMPLATES_COPY_SUCCESS="New template called %s was installed." -JTOOLBAR_CREATE_CHILD="Duplicate as child" -JTOOLBAR_OVERRIDE_CHILD="Manage overrides" +JTOOLBAR_CREATE_CHILD="Duplicate as Child" +JTOOLBAR_OVERRIDE_CHILD="Manage Overrides" COM_TEMPLATES_CROP_AREA_ERROR="Crop area not selected." COM_TEMPLATES_DIRECTORY_NOT_WRITABLE="The template folder is not writable. Some features may not work." COM_TEMPLATES_ERR_XML="Template XML data not available"