Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

\defined('_JEXEC') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Client\ClientHelper;
use Joomla\CMS\Filesystem\Path;
Expand Down Expand Up @@ -136,6 +137,124 @@ public function publish()
$this->setRedirect(Route::_($url, false));
}

/**
* Method for soft forking a template.
*
* @return boolean true on success, false otherwise
*
* @since __DEPLOY_VERSION__
*/
public function inherit()
{
// Check for request forgeries
$this->checkToken();

$app = $this->app;
$this->input->set('installtype', 'folder');
$newName = $this->input->get('new_name');
$newNameRaw = $this->input->get('new_name', null, 'string');
$templateID = $this->input->getInt('id', 0);
$defaultStyleId = $this->input->getInt('defaultStyleId', 0);
$file = $this->input->get('file');

// Access check.
if (!$this->allowEdit())
{
$app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');

return false;
}

$this->setRedirect('index.php?option=com_templates&view=template&id=' . $templateID . '&defaultStyleId=' . $defaultStyleId. 'file=' . $file);

/* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
$model = $this->getModel('Template', 'Administrator');
$model->setState('new_name', $newName);
$model->setState('defaultStyleId', $defaultStyleId);
$model->setState('tmp_prefix', uniqid('template_copy_'));
$model->setState('to_path', $app->get('tmp_path') . '/' . $model->getState('tmp_prefix'));

// Process only if we have a new name entered
if (strlen($newName) > 0)
{
if (!$this->app->getIdentity()->authorise('core.create', 'com_templates'))
{
// User is not authorised to delete
$this->setMessage(Text::_('COM_TEMPLATES_ERROR_CREATE_NOT_PERMITTED'), 'error');

return false;
}

// Set FTP credentials, if given
ClientHelper::setCredentialsFromRequest('ftp');

// Check that new name is valid
if (($newNameRaw !== null) && ($newName !== $newNameRaw))
{
$this->setMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_TEMPLATE_NAME'), 'error');

return false;
}

// Check that new name doesn't already exist
if (!$model->checkNewName())
{
$this->setMessage(Text::_('COM_TEMPLATES_ERROR_DUPLICATE_TEMPLATE_NAME'), 'error');

return false;
}

// Check that from name does exist and get the folder name
$fromName = $model->getFromName();

if (!$fromName)
{
$this->setMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error');

return false;
}

// Call model's copy method
if (!$model->inherit())
{
$this->setMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_COPY'), 'error');

return false;
}

$template = $model->getTemplateWithStyle();

if ($template && $template->parent === 1)
{
$client = ApplicationHelper::getClientInfo($template->client_id);

// Call installation model
$this->input->set('install_directory', $app->get('tmp_path') . '/' . $model->getState('tmp_prefix'));

/** @var \Joomla\Component\Installer\Administrator\Model\InstallModel $installModel */
$installModel = $this->app->bootComponent('com_installer')
->getMVCFactory()->createModel('Install', 'Administrator');
$this->app->getLanguage()->load('com_installer');

if (!$installModel->install())
{
$this->setMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_INSTALL'), 'error');

return false;
}

$this->setMessage(Text::sprintf('COM_TEMPLATES_COPY_SUCCESS', $newName));
$model->cleanup();

return true;
}

return false;
}

return false;
}

/**
* Method for copying the template.
*
Expand Down
169 changes: 161 additions & 8 deletions administrator/components/com_templates/src/Model/TemplateModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,60 @@ protected function populateState()
$this->setState('params', $params);
}

/**
* Method to get the template information.
*
* @return mixed Object if successful, false if not and internal error is set.
*
* @since 1.6
*/
public function &getTemplateWithStyle()
{
$pk = (int) $this->getState('extension.id');
$db = $this->getDbo();
$app = Factory::getApplication();

// Get the template information.
$query = $db->getQuery(true)
->select($db->quoteName(['id', 'parent', 'inherits', 'e.extension_id', 'e.client_id', 'e.element', 'e.name', 'e.manifest_cache']))
->from($db->quoteName('#__template_styles', 's'))
->where([
$db->quoteName('id') . ' = ' . $db->quote($this->getState('defaultStyleId'))
])
->join(
'LEFT',
$db->quoteName('#__extensions', 'e'),
$db->quoteName('e.extension_id') . ' = :pk'
. ' AND ' . $db->quoteName('e.type') . ' = ' . $db->quote('template')
)
->bind(':pk', $pk, ParameterType::INTEGER);
$db->setQuery($query);

try
{
$result = $db->loadObject();
}
catch (\RuntimeException $e)
{
$app->enqueueMessage($e->getMessage(), 'warning');
$this->template = false;

return false;
}

if (empty($result))
{
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EXTENSION_RECORD_NOT_FOUND'), 'error');
$this->template = false;
}
else
{
$this->template = $result;
}

return $this->template;
}

/**
* Method to get the template information.
*
Expand Down Expand Up @@ -706,6 +760,98 @@ public function getFromName()
return $this->getTemplate()->element;
}

/**
* Method to soft fork a template
*
* @return boolean true if name is not used, false otherwise
*
* @since __DEPLOY_VERSION__
*/
public function inherit()
{
$app = Factory::getApplication();
$template = $this->getTemplateWithStyle();

if ($template->parent === 0)
{
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_INHERIT'), 'error');

return false;
}

if ($template = $this->getTemplate())
{
$client = ApplicationHelper::getClientInfo($template->client_id);
$fromPath = Path::clean($client->path . '/templates/' . $template->element . '/');
$newName = $this->getState('new_name');
$defaultStyleId = $this->getState('defaultStyleId');
$toPath = $this->getState('to_path');

// Delete new folder if it exists
if (Folder::exists($toPath))
{
if (!Folder::delete($toPath))
{
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');

return false;
}
}
else
{
if (!Folder::create($toPath))
{
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');

return false;
}
}

// If there is a valid xml file soft fork it
if (!is_file($fromPath . 'templateDetails.xml'))
{
return false;
}

// Edit XML file
if (File::exists($fromPath . 'templateDetails.xml'))
{
$manifest = simplexml_load_file($fromPath . 'templateDetails.xml');
$contents = file_get_contents($fromPath . 'templateDetails.xml');
$pattern[] = '#<name>\s*' . $manifest->name . '\s*</name>#i';
$replace[] = '<name>' . $newName . '</name>';
$pattern[] = '#<languages(.*)</languages>#s';
$replace[] = '<inherits>' . $defaultStyleId . '</inherits>';
$pattern[] = '#<files>(.*)<\/files>#s';
$replace[] = '<files><filename>templateDetails.xml</filename><filename>template_preview.png</filename><filename>template_thumbnail.png</filename></files>';
$contents = preg_replace($pattern, $replace, $contents);
File::write($toPath . '/templateDetails.xml', $contents);
}

// Copy over the template thumbs
// @todo watermark the images with a `child` string or something
foreach (['template_preview.png', 'template_thumbnail.png'] as $path)
{
if (is_file($fromPath . $path))
{
if (!File::copy($fromPath . $path, $toPath . '/' . $path)) {
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');

return false;
}
}
}

return true;
}
else
{
$app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error');

return false;
}
}

/**
* Method to check if new template name already exists
*
Expand Down Expand Up @@ -838,7 +984,7 @@ protected function fixTemplateName()
{
$contents = file_get_contents($xmlFile);
$pattern[] = '#<name>\s*' . $manifest->name . '\s*</name>#i';
$replace[] = '<name>' . $newName . '</name>';
$replace[] = '<name>' . $newName . '</name><inherits>' . $oldName . '</inherits>';
$pattern[] = '#<language(.*)' . $oldName . '(.*)</language>#';
$replace[] = '<language${1}' . $newName . '${2}</language>';
$pattern[] = '#<media(.*)' . $oldName . '(.*)>#';
Expand Down Expand Up @@ -1253,18 +1399,16 @@ public function createOverride($override)
$return = $this->createTemplateOverride($override, $htmlPath);
}

if ($return)
{
$app->enqueueMessage(Text::_('COM_TEMPLATES_OVERRIDE_CREATED') . str_replace(JPATH_ROOT, '', $htmlPath));

return true;
}
else
if (!$return)
{
$app->enqueueMessage(Text::_('COM_TEMPLATES_OVERRIDE_FAILED'), 'error');

return false;
}

$app->enqueueMessage(Text::_('COM_TEMPLATES_OVERRIDE_CREATED') . str_replace(JPATH_ROOT, '', $htmlPath));

return true;
}
}

Expand All @@ -1285,6 +1429,9 @@ public function createTemplateOverride($overridePath, $htmlPath)
return $return;
}

// The template provided overrides
$templateBaseHtml = preg_replace('/' . $this->template->element . '\/html\//', $this->template->element . '/base_html/', $htmlPath);

// Get list of template folders
$folders = Folder::folders($overridePath, null, true, true);

Expand All @@ -1301,6 +1448,12 @@ public function createTemplateOverride($overridePath, $htmlPath)
}
}

// Check if there is a base_html override
if (is_dir($templateBaseHtml))
{
$overridePath = $templateBaseHtml;
}

// Get list of template files (Only get *.php file for template file)
$files = Folder::files($overridePath, '.php', true, true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,41 @@ public function getItems()
$client = ApplicationHelper::getClientInfo($item->client_id);
$item->xmldata = TemplatesHelper::parseXMLTemplateFile($client->path, $item->element);
$num = $this->updated($item->extension_id);
$defaultStyleId = (int) $this->getDefaultStyleId($item->element, $item->client_id);

if ($num)
{
$item->updated = $num;
}

if ($defaultStyleId)
{
$item->defaultStyleId = $defaultStyleId;
}
}

return $items;
}

public function getDefaultStyleId($element, $client)
{
$db = Factory::getDbo();

// Select the required fields from the table
$query = $db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__template_styles'))
->where($db->quoteName('client_id') . ' = :clientid')
->where($db->quoteName('template') . ' = :element')
->bind(':clientid', $client, ParameterType::INTEGER)
->bind(':element', $element, ParameterType::INTEGER);

// Reset the query.
$db->setQuery($query);

// Load the results as a list of stdClass objects.
return $db->loadResult();
}
/**
* Check if template extension have any updated override.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ protected function addToolbar()
// Add a copy template button
elseif ($this->type === 'home')
{
ToolbarHelper::modal('forkModal', 'fas fa-copy', 'COM_TEMPLATES_BUTTON_SOFT_FORK_TEMPLATE');
ToolbarHelper::modal('copyModal', 'fas fa-copy', 'COM_TEMPLATES_BUTTON_COPY_TEMPLATE');
}
}
Expand Down
Loading