primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,70 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Controller;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Router\Route;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Templates manager display controller.
*
* @since 1.6
*/
class DisplayController extends BaseController
{
/**
* @var string The default view.
* @since 1.6
*/
protected $default_view = 'styles';
/**
* Method to display a view.
*
* @param boolean $cachable If true, the view output will be cached
* @param boolean $urlparams An array of safe URL parameters and their variable types.
* @see \Joomla\CMS\Filter\InputFilter::clean() for valid values.
*
* @return static|boolean This object to support chaining or false on failure.
*
* @since 1.5
*/
public function display($cachable = false, $urlparams = false)
{
$view = $this->input->get('view', 'styles');
$layout = $this->input->get('layout', 'default');
$id = $this->input->getInt('id');
// For JSON requests
if ($this->app->getDocument()->getType() == 'json') {
return parent::display();
}
// Check for edit form.
if ($view == 'style' && $layout == 'edit' && !$this->checkEditId('com_templates.edit.style', $id)) {
// Somehow the person just went to the form - we don't allow that.
if (!\count($this->app->getMessageQueue())) {
$this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
}
$this->setRedirect(Route::_('index.php?option=com_templates&view=styles', false));
return false;
}
return parent::display();
}
}

View File

@ -0,0 +1,149 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Controller;
use Joomla\CMS\Application\CMSWebApplicationInterface;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\FormController;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Template style controller class.
*
* @since 1.6
*/
class StyleController extends FormController
{
/**
* The prefix to use with controller messages.
*
* @var string
* @since 1.6
*/
protected $text_prefix = 'COM_TEMPLATES_STYLE';
/**
* Method to save a template style.
*
* @param string $key The name of the primary key of the URL variable.
* @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
*
* @return boolean True if successful, false otherwise.
*
* @since 1.6
*/
public function save($key = null, $urlVar = null)
{
$this->checkToken();
if ($this->app->getDocument()->getType() === 'json') {
$model = $this->getModel('Style', 'Administrator');
$table = $model->getTable();
$data = $this->input->post->get('params', [], 'array');
$checkin = $table->hasField('checked_out');
$context = $this->option . '.edit.' . $this->context;
$item = $model->getItem($this->app->getTemplate(true)->id);
// Setting received params
$item->set('params', $data);
$data = $item->getProperties();
unset($data['xml']);
$key = $table->getKeyName();
// Access check.
if (!$this->allowSave($data, $key)) {
$this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
return false;
}
Form::addFormPath(JPATH_ADMINISTRATOR . '/components/com_templates/forms');
// Validate the posted data.
// Sometimes the form needs some posted data, such as for plugins and modules.
$form = $model->getForm($data, false);
if (!$form) {
$this->app->enqueueMessage($model->getError(), 'error');
return false;
}
// Test whether the data is valid.
$validData = $model->validate($form, $data);
if ($validData === false) {
// Get the validation messages.
$errors = $model->getErrors();
// Push up to three validation messages out to the user.
for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++) {
if ($errors[$i] instanceof \Exception) {
$this->app->enqueueMessage($errors[$i]->getMessage(), CMSWebApplicationInterface::MSG_ERROR);
} else {
$this->app->enqueueMessage($errors[$i], CMSWebApplicationInterface::MSG_ERROR);
}
}
// Save the data in the session.
$this->app->setUserState($context . '.data', $data);
return false;
}
if (!isset($validData['tags'])) {
$validData['tags'] = null;
}
// Attempt to save the data.
if (!$model->save($validData)) {
// Save the data in the session.
$this->app->setUserState($context . '.data', $validData);
$this->app->enqueueMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
return false;
}
// Save succeeded, so check-in the record.
if ($checkin && $model->checkin($validData[$key]) === false) {
// Save the data in the session.
$this->app->setUserState($context . '.data', $validData);
// Check-in failed, so go back to the record and display a notice.
$this->app->enqueueMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'error');
return false;
}
// Redirect the user and adjust session state
// Set the record data in the session.
$recordId = $model->getState($this->context . '.id');
$this->holdEditId($context, $recordId);
$this->app->setUserState($context . '.data', null);
$model->checkout($recordId);
// Invoke the postSave method to allow for the child class to access the model.
$this->postSaveHook($model, $validData);
return true;
}
return parent::save($key, $urlVar);
}
}

View File

@ -0,0 +1,145 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Controller;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Template styles list controller class.
*
* @since 1.6
*/
class StylesController extends AdminController
{
/**
* Method to clone and existing template style.
*
* @return void
*/
public function duplicate()
{
// Check for request forgeries
$this->checkToken();
$pks = (array) $this->input->post->get('cid', [], 'int');
// Remove zero values resulting from input filter
$pks = array_filter($pks);
try {
if (empty($pks)) {
throw new \Exception(Text::_('COM_TEMPLATES_NO_TEMPLATE_SELECTED'));
}
$model = $this->getModel();
$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');
}
/**
* Proxy for getModel.
*
* @param string $name The model name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return BaseDatabaseModel
*
* @since 1.6
*/
public function getModel($name = 'Style', $prefix = 'Administrator', $config = [])
{
return parent::getModel($name, $prefix, ['ignore_request' => true]);
}
/**
* Method to set the home template for a client.
*
* @return void
*
* @since 1.6
*/
public function setDefault()
{
// Check for request forgeries
$this->checkToken();
$pks = (array) $this->input->post->get('cid', [], 'int');
// Remove zero values resulting from input filter
$pks = array_filter($pks);
try {
if (empty($pks)) {
throw new \Exception(Text::_('COM_TEMPLATES_NO_TEMPLATE_SELECTED'));
}
// Pop off the first element.
$id = array_shift($pks);
/** @var \Joomla\Component\Templates\Administrator\Model\StyleModel $model */
$model = $this->getModel();
$model->setHome($id);
$this->setMessage(Text::_('COM_TEMPLATES_SUCCESS_HOME_SET'));
} catch (\Exception $e) {
$this->setMessage($e->getMessage(), 'warning');
}
$this->setRedirect('index.php?option=com_templates&view=styles');
}
/**
* Method to unset the default template for a client and for a language
*
* @return void
*
* @since 1.6
*/
public function unsetDefault()
{
// Check for request forgeries
$this->checkToken('request');
$pks = (array) $this->input->get->get('cid', [], 'int');
// Remove zero values resulting from input filter
$pks = array_filter($pks);
try {
if (empty($pks)) {
throw new \Exception(Text::_('COM_TEMPLATES_NO_TEMPLATE_SELECTED'));
}
// Pop off the first element.
$id = array_shift($pks);
/** @var \Joomla\Component\Templates\Administrator\Model\StyleModel $model */
$model = $this->getModel();
$model->unsetHome($id);
$this->setMessage(Text::_('COM_TEMPLATES_SUCCESS_HOME_UNSET'));
} catch (\Exception $e) {
$this->setMessage($e->getMessage(), 'warning');
}
$this->setRedirect('index.php?option=com_templates&view=styles');
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Extension;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
use Joomla\Component\Templates\Administrator\Service\HTML\Templates;
use Psr\Container\ContainerInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Component class for com_templates
*
* @since 4.0.0
*/
class TemplatesComponent extends MVCComponent implements BootableExtensionInterface
{
use HTMLRegistryAwareTrait;
/**
* Booting the extension. This is the function to set up the environment of the extension like
* registering new class loaders, etc.
*
* If required, some initial set up can be done from services of the container, eg.
* registering HTML services.
*
* @param ContainerInterface $container The container
*
* @return void
*
* @since 4.0.0
*/
public function boot(ContainerInterface $container)
{
$this->getRegistry()->register('templates', new Templates());
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Field;
use Joomla\CMS\Form\Field\ListField;
use Joomla\Component\Templates\Administrator\Helper\TemplatesHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Template Location field.
*
* @since 3.5
*/
class TemplatelocationField extends ListField
{
/**
* The form field type.
*
* @var string
* @since 3.5
*/
protected $type = 'TemplateLocation';
/**
* Method to get the field options.
*
* @return array The field option objects.
*
* @since 3.5
*/
public function getOptions()
{
$options = TemplatesHelper::getClientOptions();
return array_merge(parent::getOptions(), $options);
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Field;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Field\ListField;
use Joomla\Component\Templates\Administrator\Helper\TemplatesHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Template Name field.
*
* @since 3.5
*/
class TemplatenameField extends ListField
{
/**
* The form field type.
*
* @var string
* @since 3.5
*/
protected $type = 'TemplateName';
/**
* Method to get the field options.
*
* @return array The field option objects.
*
* @since 1.6
*/
public function getOptions()
{
// Get the client_id filter from the user state.
$clientId = Factory::getApplication()->getUserStateFromRequest('com_templates.styles.client_id', 'client_id', '0', 'string');
// Get the templates for the selected client_id.
$options = TemplatesHelper::getTemplateOptions($clientId);
// Merge into the parent options.
return array_merge(parent::getOptions(), $options);
}
}

View File

@ -0,0 +1,170 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Helper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Language\Text;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Template Helper class.
*
* @since 3.2
*/
abstract class TemplateHelper
{
/**
* Checks if the file is an image
*
* @param string $fileName The filename
*
* @return boolean
*
* @since 3.2
*/
public static function getTypeIcon($fileName)
{
// Get file extension
return strtolower(substr($fileName, strrpos($fileName, '.') + 1));
}
/**
* Checks if the file can be uploaded
*
* @param array $file File information
* @param string $err An error message to be returned
*
* @return boolean
*
* @since 3.2
*/
public static function canUpload($file, $err = '')
{
$params = ComponentHelper::getParams('com_templates');
if (empty($file['name'])) {
$app = Factory::getApplication();
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_UPLOAD_INPUT'), 'error');
return false;
}
// Media file names should never have executable extensions buried in them.
$executable = [
'exe', 'phtml','java', 'perl', 'py', 'asp','dll', 'go', 'jar',
'ade', 'adp', 'bat', 'chm', 'cmd', 'com', 'cpl', 'hta', 'ins', 'isp',
'jse', 'lib', 'mde', 'msc', 'msp', 'mst', 'pif', 'scr', 'sct', 'shb',
'sys', 'vb', 'vbe', 'vbs', 'vxd', 'wsc', 'wsf', 'wsh',
];
$explodedFileName = explode('.', $file['name']);
if (\count($explodedFileName) > 2) {
foreach ($executable as $extensionName) {
if (\in_array($extensionName, $explodedFileName)) {
$app = Factory::getApplication();
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EXECUTABLE'), 'error');
return false;
}
}
}
if ($file['name'] !== File::makeSafe($file['name']) || preg_match('/\s/', File::makeSafe($file['name']))) {
$app = Factory::getApplication();
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNFILENAME'), 'error');
return false;
}
$format = strtolower(File::getExt($file['name']));
$imageTypes = explode(',', $params->get('image_formats', 'gif,bmp,jpg,jpeg,png,webp'));
$sourceTypes = explode(',', $params->get('source_formats', 'txt,less,ini,xml,js,php,css,scss,sass,json'));
$fontTypes = explode(',', $params->get('font_formats', 'woff,woff2,ttf,otf'));
$archiveTypes = explode(',', $params->get('compressed_formats', 'zip'));
$allowable = array_merge($imageTypes, $sourceTypes, $fontTypes, $archiveTypes);
if ($format == '' || $format == false || (!\in_array($format, $allowable))) {
$app = Factory::getApplication();
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNFILETYPE'), 'error');
return false;
}
if (\in_array($format, $archiveTypes)) {
$zip = new \ZipArchive();
if ($zip->open($file['tmp_name']) === true) {
for ($i = 0; $i < $zip->numFiles; $i++) {
$entry = $zip->getNameIndex($i);
$endString = substr($entry, -1);
if ($endString != DIRECTORY_SEPARATOR) {
$explodeArray = explode('.', $entry);
$ext = end($explodeArray);
if (!\in_array($ext, $allowable)) {
$app = Factory::getApplication();
$app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_UNSUPPORTED_ARCHIVE'), 'error');
return false;
}
}
}
} else {
$app = Factory::getApplication();
$app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_OPEN_FAIL'), 'error');
return false;
}
}
// Max upload size set to 10 MB for Template Manager
$maxSize = (int) ($params->get('upload_limit') * 1024 * 1024);
if ($maxSize > 0 && (int) $file['size'] > $maxSize) {
$app = Factory::getApplication();
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNFILETOOLARGE'), 'error');
return false;
}
$xss_check = file_get_contents($file['tmp_name'], false, null, -1, 256);
$html_tags = [
'abbr', 'acronym', 'address', 'applet', 'area', 'audioscope', 'base', 'basefont', 'bdo', 'bgsound', 'big', 'blackface', 'blink', 'blockquote',
'body', 'bq', 'br', 'button', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'comment', 'custom', 'dd', 'del', 'dfn', 'dir', 'div',
'dl', 'dt', 'em', 'embed', 'fieldset', 'fn', 'font', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'hr', 'html',
'iframe', 'ilayer', 'img', 'input', 'ins', 'isindex', 'keygen', 'kbd', 'label', 'layer', 'legend', 'li', 'limittext', 'link', 'listing',
'map', 'marquee', 'menu', 'meta', 'multicol', 'nobr', 'noembed', 'noframes', 'noscript', 'nosmartquotes', 'object', 'ol', 'optgroup', 'option',
'param', 'plaintext', 'pre', 'rt', 'ruby', 's', 'samp', 'script', 'select', 'server', 'shadow', 'sidebar', 'small', 'spacer', 'span', 'strike',
'strong', 'style', 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'title', 'tr', 'tt', 'ul', 'var', 'wbr', 'xml',
'xmp', '!DOCTYPE', '!--',
];
foreach ($html_tags as $tag) {
// A tag is '<tagname ', so we need to add < and a space or '<tagname>'
if (stristr($xss_check, '<' . $tag . ' ') || stristr($xss_check, '<' . $tag . '>')) {
$app = Factory::getApplication();
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNIEXSS'), 'error');
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,153 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Helper;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Object\CMSObject;
use Joomla\Database\ParameterType;
use Joomla\Filesystem\Path;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Templates component helper.
*
* @since 1.6
*/
class TemplatesHelper
{
/**
* Get a list of filter options for the application clients.
*
* @return array An array of HtmlOption elements.
*/
public static function getClientOptions()
{
// Build the filter options.
$options = [];
$options[] = HTMLHelper::_('select.option', '0', Text::_('JSITE'));
$options[] = HTMLHelper::_('select.option', '1', Text::_('JADMINISTRATOR'));
return $options;
}
/**
* Get a list of filter options for the templates with styles.
*
* @param mixed $clientId The CMS client id (0:site | 1:administrator) or '*' for all.
*
* @return array
*/
public static function getTemplateOptions($clientId = '*')
{
// Build the filter options.
$db = Factory::getDbo();
$query = $db->getQuery(true);
$query->select($db->quoteName('element', 'value'))
->select($db->quoteName('name', 'text'))
->select($db->quoteName('extension_id', 'e_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('template'))
->where($db->quoteName('enabled') . ' = 1')
->order($db->quoteName('client_id') . ' ASC')
->order($db->quoteName('name') . ' ASC');
if ($clientId != '*') {
$clientId = (int) $clientId;
$query->where($db->quoteName('client_id') . ' = :clientid')
->bind(':clientid', $clientId, ParameterType::INTEGER);
}
$db->setQuery($query);
$options = $db->loadObjectList();
return $options;
}
/**
* @param string $templateBaseDir
* @param string $templateDir
*
* @return boolean|CMSObject
*/
public static function parseXMLTemplateFile($templateBaseDir, $templateDir)
{
$data = new CMSObject();
// Check of the xml file exists
$filePath = Path::clean($templateBaseDir . '/templates/' . $templateDir . '/templateDetails.xml');
if (is_file($filePath)) {
$xml = Installer::parseXMLInstallFile($filePath);
if ($xml['type'] != 'template') {
return false;
}
foreach ($xml as $key => $value) {
$data->set($key, $value);
}
}
return $data;
}
/**
* @param integer $clientId
* @param string $templateDir
*
* @return boolean|array
*
* @since 3.0
*/
public static function getPositions($clientId, $templateDir)
{
$positions = [];
$templateBaseDir = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
$filePath = Path::clean($templateBaseDir . '/templates/' . $templateDir . '/templateDetails.xml');
if (is_file($filePath)) {
// Read the file to see if it's a valid component XML file
$xml = simplexml_load_file($filePath);
if (!$xml) {
return false;
}
// Check for a valid XML root tag.
// Extensions use 'extension' as the root tag. Languages use 'metafile' instead
if ($xml->getName() != 'extension' && $xml->getName() != 'metafile') {
unset($xml);
return false;
}
$positions = (array) $xml->positions;
if (isset($positions['position'])) {
$positions = (array) $positions['position'];
} else {
$positions = [];
}
}
return $positions;
}
}

View File

@ -0,0 +1,794 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Model;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\AdminModel;
use Joomla\CMS\Object\CMSObject;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Table;
use Joomla\Database\ParameterType;
use Joomla\Filesystem\Path;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Template style model.
*
* @since 1.6
*/
class StyleModel extends AdminModel
{
/**
* The help screen key for the module.
*
* @var string
* @since 1.6
*/
protected $helpKey = 'Templates:_Edit_Style';
/**
* The help screen base URL for the module.
*
* @var string
* @since 1.6
*/
protected $helpURL;
/**
* Item cache.
*
* @var array
* @since 1.6
*/
private $_cache = [];
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
* @param ?MVCFactoryInterface $factory The factory.
*
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
* @since 3.2
*/
public function __construct($config = [], ?MVCFactoryInterface $factory = null)
{
$config = array_merge(
[
'event_before_delete' => 'onExtensionBeforeDelete',
'event_after_delete' => 'onExtensionAfterDelete',
'event_before_save' => 'onExtensionBeforeSave',
'event_after_save' => 'onExtensionAfterSave',
'events_map' => ['delete' => 'extension', 'save' => 'extension'],
],
$config
);
parent::__construct($config, $factory);
}
/**
* Method to auto-populate the model state.
*
* @note Calling getState in this method will result in recursion.
*
* @return void
*
* @since 1.6
*/
protected function populateState()
{
$app = Factory::getApplication();
// Load the User state.
$pk = $app->getInput()->getInt('id');
$this->setState('style.id', $pk);
// Load the parameters.
$params = ComponentHelper::getParams('com_templates');
$this->setState('params', $params);
}
/**
* Method to delete rows.
*
* @param array &$pks An array of item ids.
*
* @return boolean Returns true on success, false on failure.
*
* @since 1.6
* @throws \Exception
*/
public function delete(&$pks)
{
$pks = (array) $pks;
$user = $this->getCurrentUser();
$table = $this->getTable();
$context = $this->option . '.' . $this->name;
PluginHelper::importPlugin($this->events_map['delete']);
// Iterate the items to delete each one.
foreach ($pks as $pk) {
if ($table->load($pk)) {
// Access checks.
if (!$user->authorise('core.delete', 'com_templates')) {
throw new \Exception(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'));
}
// You should not delete a default style
if ($table->home != '0') {
Factory::getApplication()->enqueueMessage(Text::_('COM_TEMPLATES_STYLE_CANNOT_DELETE_DEFAULT_STYLE'), 'error');
return false;
}
// Trigger the before delete event.
$result = Factory::getApplication()->triggerEvent($this->event_before_delete, [$context, $table]);
if (\in_array(false, $result, true) || !$table->delete($pk)) {
$this->setError($table->getError());
return false;
}
// Trigger the after delete event.
Factory::getApplication()->triggerEvent($this->event_after_delete, [$context, $table]);
} else {
$this->setError($table->getError());
return false;
}
}
// Clean cache
$this->cleanCache();
return true;
}
/**
* Method to duplicate styles.
*
* @param array &$pks An array of primary key IDs.
*
* @return boolean True if successful.
*
* @throws \Exception
*/
public function duplicate(&$pks)
{
$user = $this->getCurrentUser();
// Access checks.
if (!$user->authorise('core.create', 'com_templates')) {
throw new \Exception(Text::_('JERROR_CORE_CREATE_NOT_PERMITTED'));
}
$context = $this->option . '.' . $this->name;
// Include the plugins for the save events.
PluginHelper::importPlugin($this->events_map['save']);
$table = $this->getTable();
foreach ($pks as $pk) {
if ($table->load($pk, true)) {
// Reset the id to create a new record.
$table->id = 0;
// Reset the home (don't want dupes of that field).
$table->home = 0;
// Alter the title.
$m = null;
$table->title = $this->generateNewTitle(null, null, $table->title);
if (!$table->check()) {
throw new \Exception($table->getError());
}
// Trigger the before save event.
$result = Factory::getApplication()->triggerEvent($this->event_before_save, [$context, &$table, true]);
if (\in_array(false, $result, true) || !$table->store()) {
throw new \Exception($table->getError());
}
// Trigger the after save event.
Factory::getApplication()->triggerEvent($this->event_after_save, [$context, &$table, true]);
} else {
throw new \Exception($table->getError());
}
}
// Clean cache
$this->cleanCache();
return true;
}
/**
* Method to change the title.
*
* @param integer $categoryId The id of the category.
* @param string $alias The alias.
* @param string $title The title.
*
* @return string New title.
*
* @since 1.7.1
*/
protected function generateNewTitle($categoryId, $alias, $title)
{
// Alter the title
$table = $this->getTable();
while ($table->load(['title' => $title])) {
$title = StringHelper::increment($title);
}
return $title;
}
/**
* Method to get the record form.
*
* @param array $data An optional array of data for the form to interrogate.
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
*
* @return Form A Form object on success, false on failure
*
* @since 1.6
*/
public function getForm($data = [], $loadData = true)
{
// The folder and element vars are passed when saving the form.
if (empty($data)) {
$item = $this->getItem();
$clientId = $item->client_id;
$template = $item->template;
} else {
$clientId = ArrayHelper::getValue($data, 'client_id');
$template = ArrayHelper::getValue($data, 'template');
}
// Add the default fields directory
$baseFolder = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
Form::addFieldPath($baseFolder . '/templates/' . $template . '/field');
// These variables are used to add data from the plugin XML files.
$this->setState('item.client_id', $clientId);
$this->setState('item.template', $template);
// Get the form.
$form = $this->loadForm('com_templates.style', 'style', ['control' => 'jform', 'load_data' => $loadData]);
if (empty($form)) {
return false;
}
// Modify the form based on access controls.
if (!$this->canEditState((object) $data)) {
// Disable fields for display.
$form->setFieldAttribute('home', 'disabled', 'true');
// Disable fields while saving.
// The controller has already verified this is a record you can edit.
$form->setFieldAttribute('home', 'filter', 'unset');
}
return $form;
}
/**
* Method to get the data that should be injected in the form.
*
* @return mixed The data for the form.
*
* @since 1.6
*/
protected function loadFormData()
{
// Check the session for previously entered form data.
$data = Factory::getApplication()->getUserState('com_templates.edit.style.data', []);
if (empty($data)) {
$data = $this->getItem();
}
$this->preprocessData('com_templates.style', $data);
return $data;
}
/**
* Method to get a single record.
*
* @param integer $pk The id of the primary key.
*
* @return mixed Object on success, false on failure.
*/
public function getItem($pk = null)
{
$pk = (!empty($pk)) ? $pk : (int) $this->getState('style.id');
if (!isset($this->_cache[$pk])) {
// Get a row instance.
$table = $this->getTable();
// Attempt to load the row.
$return = $table->load($pk);
// Check for a table object error.
if ($return === false && $table->getError()) {
$this->setError($table->getError());
return false;
}
// Convert to the \Joomla\CMS\Object\CMSObject before adding other data.
$properties = $table->getProperties(1);
$this->_cache[$pk] = ArrayHelper::toObject($properties, CMSObject::class);
// Convert the params field to an array.
$registry = new Registry($table->params);
$this->_cache[$pk]->params = $registry->toArray();
// Get the template XML.
$client = ApplicationHelper::getClientInfo($table->client_id);
$path = Path::clean($client->path . '/templates/' . $table->template . '/templateDetails.xml');
if (file_exists($path)) {
$this->_cache[$pk]->xml = simplexml_load_file($path);
} else {
$this->_cache[$pk]->xml = null;
}
}
return $this->_cache[$pk];
}
/**
* Method to allow derived classes to preprocess the form.
*
* @param Form $form A Form object.
* @param mixed $data The data expected for the form.
* @param string $group The name of the plugin group to import (defaults to "content").
*
* @return void
*
* @since 1.6
* @throws \Exception if there is an error in the form event.
*/
protected function preprocessForm(Form $form, $data, $group = 'content')
{
$clientId = $this->getState('item.client_id');
$template = $this->getState('item.template');
$lang = Factory::getLanguage();
$client = ApplicationHelper::getClientInfo($clientId);
if (!$form->loadFile('style_' . $client->name, true)) {
throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
}
$formFile = Path::clean($client->path . '/templates/' . $template . '/templateDetails.xml');
// Load the core and/or local language file(s).
// Default to using parent template language constants
$lang->load('tpl_' . $data->parent, $client->path)
|| $lang->load('tpl_' . $data->parent, $client->path . '/templates/' . $data->parent);
// Apply any, optional, overrides for child template language constants
$lang->load('tpl_' . $template, $client->path)
|| $lang->load('tpl_' . $template, $client->path . '/templates/' . $template);
if (file_exists($formFile)) {
// Get the template form.
if (!$form->loadFile($formFile, false, '//config')) {
throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
}
}
// Disable home field if it is default style
if (
(\is_array($data) && \array_key_exists('home', $data) && $data['home'] == '1')
|| (\is_object($data) && isset($data->home) && $data->home == '1')
) {
$form->setFieldAttribute('home', 'readonly', 'true');
}
if ($client->name === 'site' && !Multilanguage::isEnabled()) {
$form->setFieldAttribute('home', 'type', 'radio');
$form->setFieldAttribute('home', 'layout', 'joomla.form.field.radio.switcher');
}
// Attempt to load the xml file.
if (!$xml = simplexml_load_file($formFile)) {
throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
}
// Get the help data from the XML file if present.
$help = $xml->xpath('/extension/help');
if (!empty($help)) {
$helpKey = trim((string) $help[0]['key']);
$helpURL = trim((string) $help[0]['url']);
$this->helpKey = $helpKey ?: $this->helpKey;
$this->helpURL = $helpURL ?: $this->helpURL;
}
// Trigger the default form events.
parent::preprocessForm($form, $data, $group);
}
/**
* Method to save the form data.
*
* @param array $data The form data.
*
* @return boolean True on success.
*/
public function save($data)
{
// Detect disabled extension
$extension = Table::getInstance('Extension', 'Joomla\\CMS\\Table\\');
if ($extension->load(['enabled' => 0, 'type' => 'template', 'element' => $data['template'], 'client_id' => $data['client_id']])) {
$this->setError(Text::_('COM_TEMPLATES_ERROR_SAVE_DISABLED_TEMPLATE'));
return false;
}
$app = Factory::getApplication();
$table = $this->getTable();
$pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('style.id');
$isNew = true;
// Include the extension plugins for the save events.
PluginHelper::importPlugin($this->events_map['save']);
// Load the row if saving an existing record.
if ($pk > 0) {
$table->load($pk);
$isNew = false;
}
if ($app->getInput()->get('task') == 'save2copy') {
$data['title'] = $this->generateNewTitle(null, null, $data['title']);
$data['home'] = 0;
$data['assigned'] = '';
}
// Bind the data.
if (!$table->bind($data)) {
$this->setError($table->getError());
return false;
}
// Prepare the row for saving
$this->prepareTable($table);
// Check the data.
if (!$table->check()) {
$this->setError($table->getError());
return false;
}
// Trigger the before save event.
$result = Factory::getApplication()->triggerEvent($this->event_before_save, ['com_templates.style', &$table, $isNew]);
// Store the data.
if (\in_array(false, $result, true) || !$table->store()) {
$this->setError($table->getError());
return false;
}
$user = $this->getCurrentUser();
if ($user->authorise('core.edit', 'com_menus') && $table->client_id == 0) {
$n = 0;
$db = $this->getDatabase();
$user = $this->getCurrentUser();
$tableId = (int) $table->id;
$userId = (int) $user->id;
if (!empty($data['assigned']) && \is_array($data['assigned'])) {
$data['assigned'] = ArrayHelper::toInteger($data['assigned']);
// Update the mapping for menu items that this style IS assigned to.
$query = $db->getQuery(true)
->update($db->quoteName('#__menu'))
->set($db->quoteName('template_style_id') . ' = :newtsid')
->whereIn($db->quoteName('id'), $data['assigned'])
->where($db->quoteName('template_style_id') . ' != :tsid')
->where('(' . $db->quoteName('checked_out') . ' IS NULL OR ' . $db->quoteName('checked_out') . ' = :userid)')
->bind(':userid', $userId, ParameterType::INTEGER)
->bind(':newtsid', $tableId, ParameterType::INTEGER)
->bind(':tsid', $tableId, ParameterType::INTEGER);
$db->setQuery($query);
$db->execute();
$n += $db->getAffectedRows();
}
// Remove style mappings for menu items this style is NOT assigned to.
// If unassigned then all existing maps will be removed.
$query = $db->getQuery(true)
->update($db->quoteName('#__menu'))
->set($db->quoteName('template_style_id') . ' = 0');
if (!empty($data['assigned'])) {
$query->whereNotIn($db->quoteName('id'), $data['assigned']);
}
$query->where($db->quoteName('template_style_id') . ' = :templatestyleid')
->where('(' . $db->quoteName('checked_out') . ' IS NULL OR ' . $db->quoteName('checked_out') . ' = :userid)')
->bind(':userid', $userId, ParameterType::INTEGER)
->bind(':templatestyleid', $tableId, ParameterType::INTEGER);
$db->setQuery($query);
$db->execute();
$n += $db->getAffectedRows();
if ($n > 0) {
$app->enqueueMessage(Text::plural('COM_TEMPLATES_MENU_CHANGED', $n));
}
}
// Clean the cache.
$this->cleanCache();
// Trigger the after save event.
Factory::getApplication()->triggerEvent($this->event_after_save, ['com_templates.style', &$table, $isNew]);
$this->setState('style.id', $table->id);
return true;
}
/**
* Method to set a template style as home.
*
* @param integer $id The primary key ID for the style.
*
* @return boolean True if successful.
*
* @throws \Exception
*/
public function setHome($id = 0)
{
$user = $this->getCurrentUser();
$db = $this->getDatabase();
// Access checks.
if (!$user->authorise('core.edit.state', 'com_templates')) {
throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
}
$style = $this->getTable();
if (!$style->load((int) $id)) {
throw new \Exception(Text::_('COM_TEMPLATES_ERROR_STYLE_NOT_FOUND'));
}
// Detect disabled extension
$extension = Table::getInstance('Extension', 'Joomla\\CMS\\Table\\');
if ($extension->load(['enabled' => 0, 'type' => 'template', 'element' => $style->template, 'client_id' => $style->client_id])) {
throw new \Exception(Text::_('COM_TEMPLATES_ERROR_SAVE_DISABLED_TEMPLATE'));
}
$clientId = (int) $style->client_id;
$id = (int) $id;
// Reset the home fields for the client_id.
$query = $db->getQuery(true)
->update($db->quoteName('#__template_styles'))
->set($db->quoteName('home') . ' = ' . $db->quote('0'))
->where($db->quoteName('client_id') . ' = :clientid')
->where($db->quoteName('home') . ' = ' . $db->quote('1'))
->bind(':clientid', $clientId, ParameterType::INTEGER);
$db->setQuery($query);
$db->execute();
// Set the new home style.
$query = $db->getQuery(true)
->update($db->quoteName('#__template_styles'))
->set($db->quoteName('home') . ' = ' . $db->quote('1'))
->where($db->quoteName('id') . ' = :id')
->bind(':id', $id, ParameterType::INTEGER);
$db->setQuery($query);
$db->execute();
// Clean the cache.
$this->cleanCache();
return true;
}
/**
* Method to unset a template style as default for a language.
*
* @param integer $id The primary key ID for the style.
*
* @return boolean True if successful.
*
* @throws \Exception
*/
public function unsetHome($id = 0)
{
$user = $this->getCurrentUser();
$db = $this->getDatabase();
// Access checks.
if (!$user->authorise('core.edit.state', 'com_templates')) {
throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
}
$id = (int) $id;
// Lookup the client_id.
$query = $db->getQuery(true)
->select($db->quoteName(['client_id', 'home']))
->from($db->quoteName('#__template_styles'))
->where($db->quoteName('id') . ' = :id')
->bind(':id', $id, ParameterType::INTEGER);
$db->setQuery($query);
$style = $db->loadObject();
if (!is_numeric($style->client_id)) {
throw new \Exception(Text::_('COM_TEMPLATES_ERROR_STYLE_NOT_FOUND'));
}
if ($style->home == '1') {
throw new \Exception(Text::_('COM_TEMPLATES_ERROR_CANNOT_UNSET_DEFAULT_STYLE'));
}
// Set the new home style.
$query = $db->getQuery(true)
->update($db->quoteName('#__template_styles'))
->set($db->quoteName('home') . ' = ' . $db->quote('0'))
->where($db->quoteName('id') . ' = :id')
->bind(':id', $id, ParameterType::INTEGER);
$db->setQuery($query);
$db->execute();
// Clean the cache.
$this->cleanCache();
return true;
}
/**
* Get the necessary data to load an item help screen.
*
* @return object An object with key, url, and local properties for loading the item help screen.
*
* @since 1.6
*/
public function getHelp()
{
return (object) ['key' => $this->helpKey, 'url' => $this->helpURL];
}
/**
* Returns the back end template for the given style.
*
* @param int $styleId The style id
*
* @return \stdClass
*
* @since 4.2.0
*/
public function getAdminTemplate(int $styleId): \stdClass
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select($db->quoteName(['s.template', 's.params', 's.inheritable', 's.parent']))
->from($db->quoteName('#__template_styles', 's'))
->join(
'LEFT',
$db->quoteName('#__extensions', 'e'),
$db->quoteName('e.type') . ' = ' . $db->quote('template')
. ' AND ' . $db->quoteName('e.element') . ' = ' . $db->quoteName('s.template')
. ' AND ' . $db->quoteName('e.client_id') . ' = ' . $db->quoteName('s.client_id')
)
->where(
[
$db->quoteName('s.client_id') . ' = 1',
$db->quoteName('s.home') . ' = ' . $db->quote('1'),
]
);
if ($styleId) {
$query->extendWhere(
'OR',
[
$db->quoteName('s.client_id') . ' = 1',
$db->quoteName('s.id') . ' = :style',
$db->quoteName('e.enabled') . ' = 1',
]
)
->bind(':style', $styleId, ParameterType::INTEGER);
}
$query->order($db->quoteName('s.home'));
$db->setQuery($query);
return $db->loadObject();
}
/**
* Returns the front end templates.
*
* @return array
*
* @since 4.2.0
*/
public function getSiteTemplates(): array
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select($db->quoteName(['id', 'home', 'template', 's.params', 'inheritable', 'parent']))
->from($db->quoteName('#__template_styles', 's'))
->where(
[
$db->quoteName('s.client_id') . ' = 0',
$db->quoteName('e.enabled') . ' = 1',
]
)
->join(
'LEFT',
$db->quoteName('#__extensions', 'e'),
$db->quoteName('e.element') . ' = ' . $db->quoteName('s.template')
. ' AND ' . $db->quoteName('e.type') . ' = ' . $db->quote('template')
. ' AND ' . $db->quoteName('e.client_id') . ' = ' . $db->quoteName('s.client_id')
);
$db->setQuery($query);
return $db->loadObjectList('id');
}
/**
* Custom clean cache method
*
* @param string $group The cache group
* @param integer $clientId No longer used, will be removed without replacement
* @deprecated 4.3 will be removed in 6.0
*
* @return void
*
* @since 1.6
*/
protected function cleanCache($group = null, $clientId = 0)
{
parent::cleanCache('com_templates');
parent::cleanCache('_system');
}
}

View File

@ -0,0 +1,254 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\ParameterType;
use Joomla\Database\QueryInterface;
use Joomla\String\StringHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of template style records.
*
* @since 1.6
*/
class StylesModel extends ListModel
{
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
* @param ?MVCFactoryInterface $factory The factory.
*
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
* @since 3.2
*/
public function __construct($config = [], ?MVCFactoryInterface $factory = null)
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'id', 'a.id',
'title', 'a.title',
'template', 'a.template',
'home', 'a.home',
'menuitem',
];
}
parent::__construct($config, $factory);
}
/**
* Method to auto-populate the model state.
*
* Note. Calling getState in this method will result in recursion.
*
* @param string $ordering An optional ordering field.
* @param string $direction An optional direction (asc|desc).
*
* @return void
*
* @since 1.6
*/
protected function populateState($ordering = 'a.template', $direction = 'asc')
{
$app = Factory::getApplication();
if (!$app->isClient('api')) {
// Special case for the client id.
$clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
$clientId = !\in_array($clientId, [0, 1]) ? 0 : $clientId;
$this->setState('client_id', $clientId);
}
// Load the parameters.
$params = ComponentHelper::getParams('com_templates');
$this->setState('params', $params);
// List state information.
parent::populateState($ordering, $direction);
}
/**
* Method to get a store id based on model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id A prefix for the store id.
*
* @return string A store id.
*/
protected function getStoreId($id = '')
{
// Compile the store id.
$id .= ':' . $this->getState('client_id');
$id .= ':' . $this->getState('filter.search');
$id .= ':' . $this->getState('filter.template');
$id .= ':' . $this->getState('filter.menuitem');
return parent::getStoreId($id);
}
/**
* Build an SQL query to load the list data.
*
* @return QueryInterface
*/
protected function getListQuery()
{
$clientId = (int) $this->getState('client_id');
// Create a new query object.
$db = $this->getDatabase();
$query = $db->getQuery(true);
// Select the required fields from the table.
$query->select(
$this->getState(
'list.select',
[
$db->quoteName('a.id'),
$db->quoteName('a.template'),
$db->quoteName('a.title'),
$db->quoteName('a.home'),
$db->quoteName('a.client_id'),
$db->quoteName('l.title', 'language_title'),
$db->quoteName('l.image'),
$db->quoteName('l.sef', 'language_sef'),
]
)
)
->select(
[
'COUNT(' . $db->quoteName('m.template_style_id') . ') AS assigned',
$db->quoteName('extension_id', 'e_id'),
]
)
->from($db->quoteName('#__template_styles', 'a'))
->where($db->quoteName('a.client_id') . ' = :clientid')
->bind(':clientid', $clientId, ParameterType::INTEGER);
// Join on menus.
$query->join('LEFT', $db->quoteName('#__menu', 'm'), $db->quoteName('m.template_style_id') . ' = ' . $db->quoteName('a.id'))
->group(
[
$db->quoteName('a.id'),
$db->quoteName('a.template'),
$db->quoteName('a.title'),
$db->quoteName('a.home'),
$db->quoteName('a.client_id'),
$db->quoteName('l.title'),
$db->quoteName('l.image'),
$db->quoteName('l.sef'),
$db->quoteName('e.extension_id'),
]
);
// Join over the language.
$query->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.home'));
// Filter by extension enabled.
$query->join(
'LEFT',
$db->quoteName('#__extensions', 'e'),
$db->quoteName('e.element') . ' = ' . $db->quoteName('a.template')
. ' AND ' . $db->quoteName('e.client_id') . ' = ' . $db->quoteName('a.client_id')
)
->where(
[
$db->quoteName('e.enabled') . ' = 1',
$db->quoteName('e.type') . ' = ' . $db->quote('template'),
]
);
// Filter by template.
if ($template = $this->getState('filter.template')) {
$query->where($db->quoteName('a.template') . ' = :template')
->bind(':template', $template);
}
// Filter by menuitem.
$menuItemId = $this->getState('filter.menuitem');
if ($clientId === 0 && is_numeric($menuItemId)) {
// If user selected the templates styles that are not assigned to any page.
if ((int) $menuItemId === -1) {
// Only custom template styles overrides not assigned to any menu item.
$query->where(
[
$db->quoteName('a.home') . ' = ' . $db->quote('0'),
$db->quoteName('m.id') . ' IS NULL',
]
);
} else {
// If user selected the templates styles assigned to particular pages.
// Subquery to get the language of the selected menu item.
$menuItemId = (int) $menuItemId;
$menuItemLanguageSubQuery = $db->getQuery(true);
$menuItemLanguageSubQuery->select($db->quoteName('language'))
->from($db->quoteName('#__menu'))
->where($db->quoteName('id') . ' = :menuitemid');
$query->bind(':menuitemid', $menuItemId, ParameterType::INTEGER);
// Subquery to get the language of the selected menu item.
$templateStylesMenuItemsSubQuery = $db->getQuery(true);
$templateStylesMenuItemsSubQuery->select($db->quoteName('id'))
->from($db->quoteName('#__menu'))
->where($db->quoteName('template_style_id') . ' = ' . $db->quoteName('a.id'));
// Main query where clause.
$query->where('(' .
// Default template style (fallback template style to all menu items).
$db->quoteName('a.home') . ' = ' . $db->quote('1') . ' OR ' .
// Default template style for specific language (fallback template style to the selected menu item language).
$db->quoteName('a.home') . ' IN (' . $menuItemLanguageSubQuery . ') OR ' .
// Custom template styles override (only if assigned to the selected menu item).
'(' . $db->quoteName('a.home') . ' = ' . $db->quote('0') . ' AND ' . $menuItemId . ' IN (' . $templateStylesMenuItemsSubQuery . '))' .
')');
}
}
// Filter by search in title.
if ($search = $this->getState('filter.search')) {
if (stripos($search, 'id:') === 0) {
$ids = (int) substr($search, 3);
$query->where($db->quoteName('a.id') . ' = :id');
$query->bind(':id', $ids, ParameterType::INTEGER);
} else {
$search = '%' . StringHelper::strtolower($search) . '%';
$query->extendWhere(
'AND',
[
'LOWER(' . $db->quoteName('a.template') . ') LIKE :templatesearch',
'LOWER(' . $db->quoteName('a.title') . ') LIKE :title',
],
'OR'
)
->bind(':templatesearch', $search)
->bind(':title', $search);
}
}
// Add the list ordering clause.
$query->order($db->escape($this->getState('list.ordering', 'a.template')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
return $query;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,221 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Model;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Component\Templates\Administrator\Helper\TemplatesHelper;
use Joomla\Database\ParameterType;
use Joomla\Database\QueryInterface;
use Joomla\String\StringHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of template extension records.
*
* @since 1.6
*/
class TemplatesModel extends ListModel
{
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
* @param ?MVCFactoryInterface $factory The factory.
*
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
* @since 3.2
*/
public function __construct($config = [], ?MVCFactoryInterface $factory = null)
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'id', 'a.id',
'name', 'a.name',
'folder', 'a.folder',
'element', 'a.element',
'checked_out', 'a.checked_out',
'checked_out_time', 'a.checked_out_time',
'state', 'a.state',
'enabled', 'a.enabled',
'ordering', 'a.ordering',
];
}
parent::__construct($config, $factory);
}
/**
* Override parent getItems to add extra XML metadata.
*
* @return array
*
* @since 1.6
*/
public function getItems()
{
$items = parent::getItems();
foreach ($items as &$item) {
$client = ApplicationHelper::getClientInfo($item->client_id);
$item->xmldata = TemplatesHelper::parseXMLTemplateFile($client->path, $item->element);
$num = $this->updated($item->extension_id);
if ($num) {
$item->updated = $num;
}
}
return $items;
}
/**
* Check if template extension have any updated override.
*
* @param integer $exid Extension id of template.
*
* @return boolean False if records not found/else integer.
*
* @since 4.0.0
*/
public function updated($exid)
{
$db = $this->getDatabase();
// Select the required fields from the table
$query = $db->getQuery(true)
->select($db->quoteName('template'))
->from($db->quoteName('#__template_overrides'))
->where($db->quoteName('extension_id') . ' = :extensionid')
->where($db->quoteName('state') . ' = 0')
->bind(':extensionid', $exid, ParameterType::INTEGER);
// Reset the query.
$db->setQuery($query);
// Load the results as a list of stdClass objects.
$num = \count($db->loadObjectList());
if ($num > 0) {
return $num;
}
return false;
}
/**
* Build an SQL query to load the list data.
*
* @return QueryInterface
*
* @since 1.6
*/
protected function getListQuery()
{
// Create a new query object.
$db = $this->getDatabase();
$query = $db->getQuery(true);
// Select the required fields from the table.
$query->select(
$this->getState(
'list.select',
'a.extension_id, a.name, a.element, a.client_id'
)
);
$clientId = (int) $this->getState('client_id');
$query->from($db->quoteName('#__extensions', 'a'))
->where($db->quoteName('a.client_id') . ' = :clientid')
->where($db->quoteName('a.enabled') . ' = 1')
->where($db->quoteName('a.type') . ' = ' . $db->quote('template'))
->bind(':clientid', $clientId, ParameterType::INTEGER);
// Filter by search in title.
if ($search = $this->getState('filter.search')) {
if (stripos($search, 'id:') === 0) {
$ids = (int) substr($search, 3);
$query->where($db->quoteName('a.id') . ' = :id');
$query->bind(':id', $ids, ParameterType::INTEGER);
} else {
$search = '%' . StringHelper::strtolower($search) . '%';
$query->extendWhere(
'AND',
[
'LOWER(' . $db->quoteName('a.element') . ') LIKE :element',
'LOWER(' . $db->quoteName('a.name') . ') LIKE :name',
],
'OR'
)
->bind(':element', $search)
->bind(':name', $search);
}
}
// Add the list ordering clause.
$query->order($db->escape($this->getState('list.ordering', 'a.element')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
return $query;
}
/**
* Method to get a store id based on model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id A prefix for the store id.
*
* @return string A store id.
*
* @since 1.6
*/
protected function getStoreId($id = '')
{
// Compile the store id.
$id .= ':' . $this->getState('client_id');
$id .= ':' . $this->getState('filter.search');
return parent::getStoreId($id);
}
/**
* Method to auto-populate the model state.
*
* Note. Calling getState in this method will result in recursion.
*
* @param string $ordering An optional ordering field.
* @param string $direction An optional direction (asc|desc).
*
* @return void
*
* @since 1.6
*/
protected function populateState($ordering = 'a.element', $direction = 'asc')
{
// Special case for the client id.
$clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
$clientId = (!\in_array($clientId, [0, 1])) ? 0 : $clientId;
$this->setState('client_id', $clientId);
// Load the parameters.
$params = ComponentHelper::getParams('com_templates');
$this->setState('params', $params);
// List state information.
parent::populateState($ordering, $direction);
}
}

View File

@ -0,0 +1,165 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Service\HTML;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
use Joomla\Component\Templates\Administrator\Helper\TemplatesHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Html helper class.
*
* @since 1.6
*/
class Templates
{
/**
* Display the thumb for the template.
*
* @param string|object $template The name of the template or the template object.
* @deprecated 4.3 will be removed in 6.0
* The argument $template must be an object only
* @param integer $clientId No longer used, will be removed without replacement
* @deprecated 4.3 will be removed in 6.0
*
* @return string The html string
*
* @since 1.6
*/
public function thumb($template, $clientId = 0)
{
if (\is_string($template)) {
return HTMLHelper::_('image', 'template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'), [], true, -1);
}
$client = ApplicationHelper::getClientInfo($template->client_id);
if (!isset($template->xmldata)) {
$template->xmldata = TemplatesHelper::parseXMLTemplateFile($client->id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $template->name);
}
if ((isset($template->xmldata->inheritable) && (bool) $template->xmldata->inheritable) || isset($template->xmldata->parent)) {
if (isset($template->xmldata->parent) && (string) $template->xmldata->parent !== '' && file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_thumbnail.png')) {
if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png')) {
$html = HTMLHelper::_('image', 'media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'));
$html = '<button type="button" data-bs-target="#' . $template->element . '-Modal" class="thumbnail" data-bs-toggle="modal" title="' . Text::_('COM_TEMPLATES_CLICK_TO_ENLARGE') . '">' . $html . '</button>';
} elseif ((file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_preview.png'))) {
$html = HTMLHelper::_('image', 'media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'));
$html = '<button type="button" data-bs-target="#' . $template->element . '-Modal" class="thumbnail" data-bs-toggle="modal" title="' . Text::_('COM_TEMPLATES_CLICK_TO_ENLARGE') . '">' . $html . '</button>';
} else {
$html = HTMLHelper::_('image', 'template_thumb.svg', Text::_('COM_TEMPLATES_PREVIEW'), ['style' => 'width:200px; height:120px;']);
}
} elseif (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png')) {
$html = HTMLHelper::_('image', 'media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'));
if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png')) {
$html = '<button type="button" data-bs-target="#' . $template->element . '-Modal" class="thumbnail" data-bs-toggle="modal" title="' . Text::_('COM_TEMPLATES_CLICK_TO_ENLARGE') . '">' . $html . '</button>';
}
} else {
$html = HTMLHelper::_('image', 'template_thumb.svg', Text::_('COM_TEMPLATES_PREVIEW'), ['style' => 'width:200px; height:120px;']);
}
} elseif (file_exists($client->path . '/templates/' . $template->element . '/template_thumbnail.png')) {
$html = HTMLHelper::_('image', (($template->client_id == 0) ? Uri::root(true) : Uri::root(true) . '/administrator/') . '/templates/' . $template->element . '/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'), [], false, -1);
if (file_exists($client->path . '/templates/' . $template->element . '/template_preview.png')) {
$html = '<button type="button" data-bs-target="#' . $template->element . '-Modal" class="thumbnail" data-bs-toggle="modal" title="' . Text::_('COM_TEMPLATES_CLICK_TO_ENLARGE') . '">' . $html . '</button>';
}
} else {
$html = HTMLHelper::_('image', 'template_thumb.svg', Text::_('COM_TEMPLATES_PREVIEW'), ['style' => 'width:200px; height:120px;']);
}
return $html;
}
/**
* Renders the html for the modal linked to thumb.
*
* @param string|object $template The name of the template or the template object.
* @deprecated 4.3 will be removed in 6.0
* The argument $template must be an object only
* @param integer $clientId No longer used, will be removed without replacement
* @deprecated 4.3 will be removed in 6.0
*
* @return string The html string
*
* @since 3.4
*/
public function thumbModal($template, $clientId = 0)
{
if (\is_string($template)) {
return HTMLHelper::_('image', 'template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'), [], true, -1);
}
$html = '';
$thumb = '';
$preview = '';
$client = ApplicationHelper::getClientInfo($template->client_id);
if (!isset($template->xmldata)) {
$template->xmldata = TemplatesHelper::parseXMLTemplateFile($client->id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $template->name);
}
if ((isset($template->xmldata->inheritable) && (bool) $template->xmldata->inheritable) || isset($template->xmldata->parent)) {
if (isset($template->xmldata->parent) && (string) $template->xmldata->parent !== '') {
if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png')) {
$thumb = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . 'administrator') . 'media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png';
if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png')) {
$preview = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . '/administrator') . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png';
}
} else {
$thumb = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . 'administrator') . 'media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_thumbnail.png';
if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_preview.png')) {
$preview = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . '/administrator') . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_preview.png';
}
}
} elseif (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png')) {
$thumb = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . '/administrator') . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png';
if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png')) {
$preview = Uri::root(true) . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png';
}
}
} elseif (file_exists($client->path . '/templates/' . $template->element . '/template_thumbnail.png')) {
$thumb = (($template->client_id == 0) ? Uri::root(true) : Uri::root(true) . 'administrator') . '/templates/' . $template->element . '/template_thumbnail.png';
if (file_exists($client->path . '/templates/' . $template->element . '/template_preview.png')) {
$preview = (($template->client_id == 0) ? Uri::root(true) : Uri::root(true) . '/administrator') . '/templates/' . $template->element . '/template_preview.png';
}
}
if ($thumb !== '' && $preview !== '') {
$footer = '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">'
. Text::_('JTOOLBAR_CLOSE') . '</button>';
$html .= HTMLHelper::_(
'bootstrap.renderModal',
$template->element . '-Modal',
[
'title' => Text::sprintf('COM_TEMPLATES_SCREENSHOT', ucfirst($template->name)),
'height' => '500px',
'width' => '800px',
'footer' => $footer,
],
'<div><img src="' . $preview . '" class="mw-100" alt="' . $template->name . '"></div>'
);
}
return $html;
}
}

View File

@ -0,0 +1,159 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\Table;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\ParameterType;
use Joomla\Event\DispatcherInterface;
use Joomla\Registry\Registry;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Template style table class.
*
* @since 1.6
*/
class StyleTable extends Table
{
/**
* Constructor
*
* @param DatabaseDriver $db Database connector object
* @param ?DispatcherInterface $dispatcher Event dispatcher for this table
*
* @since 1.6
*/
public function __construct(DatabaseDriver $db, ?DispatcherInterface $dispatcher = null)
{
parent::__construct('#__template_styles', 'id', $db, $dispatcher);
}
/**
* Overloaded bind function to pre-process the params.
*
* @param array $array Named array
* @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
*
* @return null|string null if operation was satisfactory, otherwise returns an error
*
* @since 1.6
*/
public function bind($array, $ignore = '')
{
if (isset($array['params']) && \is_array($array['params'])) {
$registry = new Registry($array['params']);
$array['params'] = (string) $registry;
}
// Verify that the default style is not unset
if ($array['home'] == '0' && $this->home == '1') {
$this->setError(Text::_('COM_TEMPLATES_ERROR_CANNOT_UNSET_DEFAULT_STYLE'));
return false;
}
return parent::bind($array, $ignore);
}
/**
* Overloaded check method to ensure data integrity.
*
* @return boolean True on success.
*
* @since 1.6
*/
public function check()
{
try {
parent::check();
} catch (\Exception $e) {
$this->setError($e->getMessage());
return false;
}
if (empty($this->title)) {
$this->setError(Text::_('COM_TEMPLATES_ERROR_STYLE_REQUIRES_TITLE'));
return false;
}
return true;
}
/**
* Overloaded store method to ensure unicity of default style.
*
* @param boolean $updateNulls True to update fields even if they are null.
*
* @return boolean True on success.
*
* @since 1.6
*/
public function store($updateNulls = false)
{
if ($this->home != '0') {
$clientId = (int) $this->client_id;
$query = $this->_db->getQuery(true)
->update($this->_db->quoteName('#__template_styles'))
->set($this->_db->quoteName('home') . ' = ' . $this->_db->quote('0'))
->where($this->_db->quoteName('client_id') . ' = :clientid')
->where($this->_db->quoteName('home') . ' = :home')
->bind(':clientid', $clientId, ParameterType::INTEGER)
->bind(':home', $this->home);
$this->_db->setQuery($query);
$this->_db->execute();
}
return parent::store($updateNulls);
}
/**
* Overloaded store method to unsure existence of a default style for a template.
*
* @param mixed $pk An optional primary key value to delete. If not set the instance property value is used.
*
* @return boolean True on success.
*
* @since 1.6
*/
public function delete($pk = null)
{
$k = $this->_tbl_key;
$pk = \is_null($pk) ? $this->$k : $pk;
if (!\is_null($pk)) {
$clientId = (int) $this->client_id;
$query = $this->_db->getQuery(true)
->select($this->_db->quoteName('id'))
->from($this->_db->quoteName('#__template_styles'))
->where($this->_db->quoteName('client_id') . ' = :clientid')
->where($this->_db->quoteName('template') . ' = :template')
->bind(':template', $this->template)
->bind(':clientid', $clientId, ParameterType::INTEGER);
$this->_db->setQuery($query);
$results = $this->_db->loadColumn();
if (\count($results) == 1 && $results[0] == $pk) {
$this->setError(Text::_('COM_TEMPLATES_ERROR_CANNOT_DELETE_LAST_STYLE'));
return false;
}
}
return parent::delete($pk);
}
}

View File

@ -0,0 +1,161 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\View\Style;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Toolbar\ToolbarHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View to edit a template style.
*
* @since 1.6
*/
class HtmlView extends BaseHtmlView
{
/**
* The item
*
* @var \stdClass
*/
protected $item;
/**
* The form object
*
* @var \Joomla\CMS\Form\Form
*/
protected $form;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*/
protected $state;
/**
* The actions the user is authorised to perform
*
* @var \Joomla\Registry\Registry
*
* @since 4.0.0
*/
protected $canDo;
/**
* Array of fieldsets not to display
*
* @var string[]
*
* @since 5.2.0
*/
public $ignore_fieldsets = [];
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void
*
* @since 1.6
*/
public function display($tpl = null)
{
$this->item = $this->get('Item');
$this->state = $this->get('State');
$this->form = $this->get('Form');
$this->canDo = ContentHelper::getActions('com_templates');
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 1.6
*/
protected function addToolbar()
{
Factory::getApplication()->getInput()->set('hidemainmenu', true);
$isNew = ($this->item->id == 0);
$canDo = $this->canDo;
$toolbar = $this->getDocument()->getToolbar();
ToolbarHelper::title(
$isNew ? Text::_('COM_TEMPLATES_MANAGER_ADD_STYLE')
: Text::_('COM_TEMPLATES_MANAGER_EDIT_STYLE'),
'paint-brush thememanager'
);
// If not checked out, can save the item.
if ($canDo->get('core.edit')) {
$toolbar->apply('style.apply');
}
$saveGroup = $toolbar->dropdownButton('save-group');
$saveGroup->configure(
function (Toolbar $childBar) use ($canDo, $isNew) {
// If not checked out, can save the item.
if ($canDo->get('core.edit')) {
$childBar->save('style.save');
}
// If an existing item, can save to a copy.
if (!$isNew && $canDo->get('core.create')) {
$childBar->save2copy('style.save2copy');
}
}
);
if (empty($this->item->id)) {
$toolbar->cancel('style.cancel', 'JTOOLBAR_CANCEL');
} else {
$toolbar->cancel('style.cancel');
}
$toolbar->divider();
// Get the help information for the template item.
$lang = $this->getLanguage();
$help = $this->get('Help');
if ($lang->hasKey($help->url)) {
$debug = $lang->setDebug(false);
$url = Text::_($help->url);
$lang->setDebug($debug);
} else {
$url = null;
}
$toolbar->help($help->key, false, $url);
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\View\Style;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View to edit a template style.
*
* @since 1.6
*/
class JsonView extends BaseHtmlView
{
/**
* The item
*
* @var \stdClass
*/
protected $item;
/**
* The form object
*
* @var \Joomla\CMS\Form\Form
*/
protected $form;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*/
protected $state;
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return mixed A string if successful, otherwise an Error object.
*
* @since 1.6
*/
public function display($tpl = null)
{
try {
$this->item = $this->get('Item');
} catch (\Exception $e) {
$app = Factory::getApplication();
$app->enqueueMessage($e->getMessage(), 'error');
return false;
}
$paramsList = $this->item->getProperties();
unset($paramsList['xml']);
$paramsList = json_encode($paramsList);
return $paramsList;
}
}

View File

@ -0,0 +1,161 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\View\Styles;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View class for a list of template styles.
*
* @since 1.6
*/
class HtmlView extends BaseHtmlView
{
/**
* An array of items
*
* @var array
*/
protected $items;
/**
* The pagination object
*
* @var \Joomla\CMS\Pagination\Pagination
*/
protected $pagination;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*/
protected $state;
/**
* Form object for search filters
*
* @var \Joomla\CMS\Form\Form
*
* @since 4.0.0
*/
public $filterForm;
/**
* The active search filters
*
* @var array
* @since 4.0.0
*/
public $activeFilters;
/**
* Is the parameter enabled to show template positions in the frontend?
*
* @var boolean
* @since 4.0.0
*/
public $preview;
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void
*/
public function display($tpl = null)
{
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
$this->total = $this->get('Total');
$this->filterForm = $this->get('FilterForm');
$this->activeFilters = $this->get('ActiveFilters');
$this->preview = ComponentHelper::getParams('com_templates')->get('template_positions_display');
// Remove the menu item filter for administrator styles.
if ((int) $this->state->get('client_id') !== 0) {
unset($this->activeFilters['menuitem']);
$this->filterForm->removeField('menuitem', 'filter');
}
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 1.6
*/
protected function addToolbar()
{
$canDo = ContentHelper::getActions('com_templates');
$clientId = (int) $this->get('State')->get('client_id');
$toolbar = $this->getDocument()->getToolbar();
// Add a shortcut to the templates list view.
$toolbar->linkButton('templates', 'COM_TEMPLATES_MANAGER_TEMPLATES')
->url('index.php?option=com_templates&view=templates&client_id=' . $clientId)
->icon('icon-code thememanager');
// Set the title.
if ($clientId === 1) {
ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_STYLES_ADMIN'), 'paint-brush thememanager');
} else {
ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_STYLES_SITE'), 'paint-brush thememanager');
}
if ($canDo->get('core.edit.state')) {
$toolbar->makeDefault('styles.setDefault', 'COM_TEMPLATES_TOOLBAR_SET_HOME');
$toolbar->divider();
}
if ($canDo->get('core.create')) {
$toolbar->standardButton('duplicate', 'JTOOLBAR_DUPLICATE', 'styles.duplicate')
->listCheck(true)
->icon('icon-copy');
$toolbar->divider();
}
if ($canDo->get('core.delete')) {
$toolbar->delete('styles.delete')
->message('JGLOBAL_CONFIRM_DELETE')
->listCheck(true);
$toolbar->divider();
}
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
$toolbar->preferences('com_templates');
$toolbar->divider();
}
$toolbar->help('Templates:_Styles');
}
}

View File

@ -0,0 +1,418 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\View\Template;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Toolbar\Button\DropdownButton;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\CMS\Uri\Uri;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View to edit a template.
*
* @since 1.6
*/
class HtmlView extends BaseHtmlView
{
/**
* The Model state
*
* @var \Joomla\Registry\Registry
*/
protected $state;
/**
* The template details
*
* @var \stdClass|false
*/
protected $template;
/**
* For loading the source form
*
* @var Form
*/
protected $form;
/**
* For loading source file contents
*
* @var array
*/
protected $source;
/**
* Extension id
*
* @var integer
*/
protected $id;
/**
* Encrypted file path
*
* @var string
*/
protected $file;
/**
* List of available overrides
*
* @var array
*/
protected $overridesList;
/**
* Name of the present file
*
* @var string
*/
protected $fileName;
/**
* Type of the file - image, source, font
*
* @var string
*/
protected $type;
/**
* For loading image information
*
* @var array
*/
protected $image;
/**
* Template id for showing preview button
*
* @var \stdClass
*/
protected $preview;
/**
* For loading font information
*
* @var array
*/
protected $font;
/**
* A nested array containing list of files and folders
*
* @var array
*/
protected $files;
/**
* An array containing a list of compressed files
*
* @var array
*/
protected $archive;
/**
* The state of installer override plugin.
*
* @var array
*
* @since 4.0.0
*/
protected $pluginState;
/**
* A nested array containing list of files and folders in the media folder
*
* @var array
*
* @since 4.1.0
*/
protected $mediaFiles;
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void|boolean
*/
public function display($tpl = null)
{
$app = Factory::getApplication();
$this->file = $app->getInput()->get('file', '');
$this->fileName = InputFilter::getInstance()->clean(base64_decode($this->file), 'string');
$explodeArray = explode('.', $this->fileName);
$ext = end($explodeArray);
$this->files = $this->get('Files');
$this->mediaFiles = $this->get('MediaFiles');
$this->state = $this->get('State');
$this->template = $this->get('Template');
$this->preview = $this->get('Preview');
$this->pluginState = PluginHelper::isEnabled('installer', 'override');
$this->updatedList = $this->get('UpdatedList');
$this->styles = $this->get('AllTemplateStyles');
$this->stylesHTML = '';
$params = ComponentHelper::getParams('com_templates');
$imageTypes = explode(',', $params->get('image_formats', 'gif,bmp,jpg,jpeg,png,webp'));
$sourceTypes = explode(',', $params->get('source_formats', 'txt,less,ini,xml,js,php,css,scss,sass,json'));
$fontTypes = explode(',', $params->get('font_formats', 'woff,woff2,ttf,otf'));
$archiveTypes = explode(',', $params->get('compressed_formats', 'zip'));
if (\in_array($ext, $sourceTypes)) {
$this->form = $this->get('Form');
$this->form->setFieldAttribute('source', 'syntax', $ext);
$this->source = $this->get('Source');
$this->type = 'file';
} elseif (\in_array($ext, $imageTypes)) {
try {
$this->image = $this->get('Image');
$this->type = 'image';
} catch (\RuntimeException $exception) {
$app->enqueueMessage(Text::_('COM_TEMPLATES_GD_EXTENSION_NOT_AVAILABLE'));
$this->type = 'home';
}
} elseif (\in_array($ext, $fontTypes)) {
$this->font = $this->get('Font');
$this->type = 'font';
} elseif (\in_array($ext, $archiveTypes)) {
$this->archive = $this->get('Archive');
$this->type = 'archive';
} else {
$this->type = 'home';
}
$this->overridesList = $this->get('OverridesList');
$this->id = $this->state->get('extension.id');
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
$app->enqueueMessage(implode("\n", $errors));
return false;
}
$this->addToolbar();
if (!$this->getCurrentUser()->authorise('core.admin')) {
$this->setLayout('readonly');
}
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @since 1.6
*
* @return void
*/
protected function addToolbar()
{
$app = Factory::getApplication();
$user = $this->getCurrentUser();
$toolbar = $this->getDocument()->getToolbar();
$app->getInput()->set('hidemainmenu', true);
// User is global SuperUser
$isSuperUser = $user->authorise('core.admin');
ToolbarHelper::title(Text::sprintf('COM_TEMPLATES_MANAGER_VIEW_TEMPLATE', ucfirst($this->template->name)), 'icon-code thememanager');
// Add a Template preview button
if ($this->type === 'home') {
$client = (int) $this->preview->client_id === 1 ? 'administrator/' : '';
$toolbar->linkButton('preview', 'COM_TEMPLATES_BUTTON_PREVIEW')
->url(Uri::root() . $client . 'index.php?tp=1&templateStyle=' . $this->preview->id)
->icon('icon-image')
->attributes(['target' => '_new']);
}
// Only show file edit buttons for global SuperUser
if ($isSuperUser) {
switch ($this->type) {
case 'file':
$toolbar->apply('template.apply');
$toolbar->save('template.save');
$toolbar->cancel('template.close', 'COM_TEMPLATES_BUTTON_CLOSE_FILE');
break;
case 'image':
// Add a Crop and Resize button
$toolbar->standardButton('crop', 'COM_TEMPLATES_BUTTON_CROP', 'template.cropImage')
->listCheck(false)
->icon('icon-crop');
ToolbarHelper::modal('resizeModal', 'icon-expand', 'COM_TEMPLATES_BUTTON_RESIZE');
$toolbar->cancel('template.close', 'COM_TEMPLATES_BUTTON_CLOSE_FILE');
break;
case 'archive':
// Add an extract button
$toolbar->standardButton('extract', 'COM_TEMPLATES_BUTTON_EXTRACT_ARCHIVE', 'template.extractArchive')
->listCheck(false)
->icon('icon-chevron-down');
break;
case 'home':
// Add a copy/child template button
if (isset($this->template->xmldata->inheritable) && (string) $this->template->xmldata->inheritable === '1') {
ToolbarHelper::modal('childModal', 'icon-copy', 'COM_TEMPLATES_BUTTON_TEMPLATE_CHILD');
} elseif (empty($this->template->xmldata->parent) && empty($this->template->xmldata->namespace)) {
// We can't copy parent templates nor namespaced templates
ToolbarHelper::modal('copyModal', 'icon-copy', 'COM_TEMPLATES_BUTTON_COPY_TEMPLATE');
}
break;
}
}
// Only show file manage buttons for global SuperUser
if ($isSuperUser) {
if ($this->type === 'home') {
// Add Manage folders button
ToolbarHelper::modal('folderModal', 'icon-folder icon white', 'COM_TEMPLATES_BUTTON_FOLDERS');
// Add a new file button
ToolbarHelper::modal('fileModal', 'icon-file', 'COM_TEMPLATES_BUTTON_FILE');
} else {
// Add a Rename file Button
ToolbarHelper::modal('renameModal', 'icon-sync', 'COM_TEMPLATES_BUTTON_RENAME_FILE');
// Add a Delete file Button
ToolbarHelper::modal('deleteModal', 'icon-trash', 'COM_TEMPLATES_BUTTON_DELETE_FILE', 'btn-danger');
}
}
if (\count($this->updatedList) !== 0 && $this->pluginState && $this->type === 'home') {
/** @var DropdownButton $dropdown */
$dropdown = $toolbar->dropdownButton('override-group', 'COM_TEMPLATES_BUTTON_CHECK')
->toggleSplit(false)
->icon('icon-ellipsis-h')
->buttonClass('btn btn-action')
->form('updateForm')
->listCheck(true);
$childBar = $dropdown->getChildToolbar();
$childBar->publish('template.publish')
->text('COM_TEMPLATES_BUTTON_CHECK_LIST_ENTRY')
->form('updateForm')
->listCheck(true);
$childBar->unpublish('template.unpublish')
->text('COM_TEMPLATES_BUTTON_UNCHECK_LIST_ENTRY')
->form('updateForm')
->listCheck(true);
$childBar->unpublish('template.deleteOverrideHistory')
->text('COM_TEMPLATES_BUTTON_DELETE_LIST_ENTRY')
->form('updateForm')
->listCheck(true);
}
if (!\in_array($this->type, ['image', 'file'])) {
$toolbar->cancel('template.cancel');
}
$toolbar->divider();
$toolbar->help('Templates:_Customise');
}
/**
* Method for creating the collapsible tree.
*
* @param array $array The value of the present node for recursion
*
* @return string
*
* @note Uses recursion
* @since 3.2
*/
protected function directoryTree($array)
{
$temp = $this->files;
$this->files = $array;
$txt = $this->loadTemplate('tree');
$this->files = $temp;
return $txt;
}
/**
* Method for listing the folder tree in modals.
*
* @param array $array The value of the present node for recursion
*
* @return string
*
* @note Uses recursion
* @since 3.2
*/
protected function folderTree($array)
{
$temp = $this->files;
$this->files = $array;
$txt = $this->loadTemplate('folders');
$this->files = $temp;
return $txt;
}
/**
* Method for creating the collapsible tree.
*
* @param array $array The value of the present node for recursion
*
* @return string
*
* @note Uses recursion
* @since 4.1.0
*/
protected function mediaTree($array)
{
$temp = $this->mediaFiles;
$this->mediaFiles = $array;
$txt = $this->loadTemplate('tree_media');
$this->mediaFiles = $temp;
return $txt;
}
/**
* Method for listing the folder tree in modals.
*
* @param array $array The value of the present node for recursion
*
* @return string
*
* @note Uses recursion
* @since 4.1.0
*/
protected function mediaFolderTree($array)
{
$temp = $this->mediaFiles;
$this->mediaFiles = $array;
$txt = $this->loadTemplate('media_folders');
$this->mediaFiles = $temp;
return $txt;
}
}

View File

@ -0,0 +1,159 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_templates
*
* @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Templates\Administrator\View\Templates;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Toolbar\ToolbarHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View class for a list of templates.
*
* @since 1.6
*/
class HtmlView extends BaseHtmlView
{
/**
* The list of templates
*
* @var array
* @since 1.6
*/
protected $items;
/**
* The pagination object
*
* @var object
* @since 1.6
*/
protected $pagination;
/**
* The model state
*
* @var object
* @since 1.6
*/
protected $state;
/**
* @var string
* @since 3.2
*/
protected $file;
/**
* Form object for search filters
*
* @var \Joomla\CMS\Form\Form
*
* @since 4.0.0
*/
public $filterForm;
/**
* The active search filters
*
* @var array
* @since 4.0.0
*/
public $activeFilters;
/**
* Is the parameter enabled to show template positions in the frontend?
*
* @var boolean
* @since 4.0.0
*/
public $preview;
/**
* The state of installer override plugin.
*
* @var array
*
* @since 4.0.0
*/
protected $pluginState;
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void
*
* @since 1.6
*/
public function display($tpl = null)
{
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
$this->total = $this->get('Total');
$this->filterForm = $this->get('FilterForm');
$this->activeFilters = $this->get('ActiveFilters');
$this->preview = ComponentHelper::getParams('com_templates')->get('template_positions_display');
$this->file = base64_encode('home');
$this->pluginState = PluginHelper::isEnabled('installer', 'override');
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 1.6
*/
protected function addToolbar()
{
$canDo = ContentHelper::getActions('com_templates');
$clientId = (int) $this->get('State')->get('client_id');
$toolbar = $this->getDocument()->getToolbar();
// Add a shortcut to the styles list view.
$toolbar->linkButton('', 'COM_TEMPLATES_MANAGER_STYLES_BUTTON')
->url('index.php?option=com_templates&view=styles&client_id=' . $clientId)
->icon('icon-brush thememanager');
// Set the title.
if ($clientId === 1) {
ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_TEMPLATES_ADMIN'), 'icon-code thememanager');
} else {
ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_TEMPLATES_SITE'), 'icon-code thememanager');
}
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
$toolbar->preferences('com_templates');
$toolbar->divider();
}
$toolbar->help('Templates:_Templates');
}
}