From 811ab3f5f2830638d8e11b4f85385c5fcb895bc9 Mon Sep 17 00:00:00 2001
From: Anurag <30978328+Anu1601CS@users.noreply.github.com>
Date: Thu, 6 Sep 2018 16:30:11 +0530
Subject: [PATCH] [4.0][GSoC 2018] Improve Override Management (#21851)
* Load correct core files of override files (#2)
Start implements loadcorefile() in administrator/components/com_templates/Model/TemplateModel.php
* CS (#3) Coding Standards
* codingstandards
* codingstandards (#4)
* Test (#6)
Phase 2 (2 part) Mechanism to find correct core file and implementation.
* Remove Notice: Only available for html-folder
* Remove Warning if core file not found (#11)
Thanks.
So one part of the issue https://github.com/joomla-projects/gsoc18_override_management/issues/12 is done.
* Implement the diff view in template manager
Implement the diff view in template manager
* coding standard (#17)
* fix diff (#18) Fix bug in path in case of administrator template override.
Fix bug in path in case of administrator template override.
* Notification after update and TEST (#16)
Find changed files of overridden files and show message.
* coding standard (#21)
* correction
* correction (#26)
* Correcthtmlpath (#27)
* correction
* change oldhtml to newhtml
* List of updated override files. (#30)
* addcss (#34)
* Final Product (#39)
Core and Diff view
Updated override history list.
Quick icon notification plugin.
Override control plugin.
* save 3 lines :)
* New feature show status. (#47)
show status in com_template view templates
* link
* corrected namespace
* Button to Switch (#35)
* wip add Switcher
* wip style switcher
* wip style switch make inline and change on off text
* wip start with js
* wip js
* wip delete buttons and make js more robust
* wip save to storage
* wip delete old code
* wip
* wip lint
* wip css
* set default value for switcher
* wip make switcher blue
* wip
* wip
* build
* correct names
* create new functions
* fist test code
* use onchange
* undo installer.min.js
* add forgotten new line at the end of css file
* correct align
* correct compare.es6 - only deleted the toggle part
* correct compare.js - only deleted the toggle part
* wip
* reduce timeout
* wrap in funcitons
* wip
* add use strict to both js-files(compare and toggle)
* add the timeout value of 500 again, because 200 are not enought in my case
* use css class 'active' for toggle views
* add strict
* time out for editor
* wip
* improvments use newActive and switch
* correction
* width of switcher-spans
* correct align
* do not use global
* wip
* removed timeouts
* JTEXT to TEXT
* forgotton last line
* deleted duplicated comments
* css fix align
* use unnamed functions in es6
* Sql files for fix database (#50)
* sql files for database fix
* delete space
* Suggestion for displaying Dates in view updates files (#52)
Correct Dates and do not use date of file any more
* Store Date as UTC and show it in server time zone (#57)
* modified and created date are created and stored in UTC
* convert dates for displaying in model
* spar a loop
* normalize timezone in view
* use language constants for dateformat
* JToolbarHelper to ToolbarHelper
* CS
* namespace
* plural
* name
* clean
* text
* fx
* sin
* files
* s
* Suggestion for language strings (#60)
* language strings
* correct typo
* delete media folder plg_quickicon
* add folder plg_quickicon to build/media_src
* delete files in media folder
* Move media folder - System (#66)
* multi
* cs
* delete files in media folder for joomla toolbar (#67)
* Fix button switchers style. (#70)
* button
* CS
* changed uitab.addTab for updated files
* Bring back core.js changes. (#69)
* core.js
* const
* fix
* form
* core
* hound
* CS
* scopr
* grid
* alpha
* cs
* lang
* only override file
* lang
* override lang installer
* Cs
* sub
* Update list of core extensions (#71)
* Language changes (#76)
* update
* Update en-GB.com_templates.ini
* override JLIB_HTML_PUBLISH_ITEM
this is the hover text on the publish icon in the list of files
* Change icon (#74)
change the icon to use an outline for more consistency
* lang
* not core (#75)
* not core
* Update en-GB.plg_installer_override.ini
* namespace
* cs
* Updated files (#82)
* Update default_updated_files.php
* Update en-GB.com_templates.ini
* Update en-GB.com_templates.ini (#81)
* Update en-GB.plg_quickicon_overridecheck.ini (#80)
* Update en-GB.plg_quickicon_overridecheck.ini (#79)
* remove space (#78)
* Update en-GB.plg_quickicon_overridecheck.ini
* Update en-GB.plg_quickicon_overridecheck.sys.ini
* remove hardcoded id
* null get function
* state
* clean
* More changes "core" to "original" (#85)
* cs
* update
* plural
---
.../others/mysql/utf8mb4-conversion-02.sql | 4 +-
.../sql/updates/mysql/4.0.0-2018-07-19.sql | 18 +
.../updates/postgresql/4.0.0-2018-07-19.sql | 18 +
.../com_installer/Model/UpdateModel.php | 3 +
.../com_joomlaupdate/Model/UpdateModel.php | 23 +-
.../Controller/TemplateController.php | 110 ++++-
.../com_templates/Model/TemplateModel.php | 464 +++++++++++++++++-
.../com_templates/Model/TemplatesModel.php | 43 +-
.../com_templates/View/Template/HtmlView.php | 23 +-
.../com_templates/View/Templates/HtmlView.php | 15 +-
.../components/com_templates/forms/source.xml | 38 +-
.../com_templates/tmpl/template/default.php | 81 ++-
.../tmpl/template/default_updated_files.php | 89 ++++
.../com_templates/tmpl/templates/default.php | 56 ++-
.../language/en-GB/en-GB.com_templates.ini | 29 +-
.../en-GB/en-GB.plg_installer_override.ini | 9 +
.../en-GB.plg_installer_override.sys.ini | 7 +
.../en-GB.plg_quickicon_overridecheck.ini | 14 +
.../en-GB.plg_quickicon_overridecheck.sys.ini | 7 +
.../css/admin-templates-default.css | 55 +++
.../js/toolbar-button/toolbar-button.js | 4 +-
.../js/admin-template-compare.es6.js | 61 +++
.../js/admin-template-toggle-switch.es6.js | 126 +++++
.../js/overridecheck.es6.js | 69 +++
build/media_src/system/js/core.es6.js | 29 +-
build/media_src/system/js/multiselect.es6.js | 2 +-
installation/sql/mysql/joomla.sql | 23 +
installation/sql/postgresql/joomla.sql | 21 +
libraries/cms/html/grid.php | 13 +-
libraries/cms/html/jgrid.php | 53 +-
libraries/src/Extension/ExtensionHelper.php | 2 +
.../src/Toolbar/Button/StandardButton.php | 18 +-
libraries/src/Toolbar/ToolbarHelper.php | 5 +-
plugins/installer/override/override.php | 402 +++++++++++++++
plugins/installer/override/override.xml | 19 +
.../quickicon/overridecheck/overridecheck.php | 110 +++++
.../quickicon/overridecheck/overridecheck.xml | 32 ++
37 files changed, 2004 insertions(+), 91 deletions(-)
create mode 100644 administrator/components/com_admin/sql/updates/mysql/4.0.0-2018-07-19.sql
create mode 100644 administrator/components/com_admin/sql/updates/postgresql/4.0.0-2018-07-19.sql
create mode 100644 administrator/components/com_templates/tmpl/template/default_updated_files.php
create mode 100644 administrator/language/en-GB/en-GB.plg_installer_override.ini
create mode 100644 administrator/language/en-GB/en-GB.plg_installer_override.sys.ini
create mode 100644 administrator/language/en-GB/en-GB.plg_quickicon_overridecheck.ini
create mode 100644 administrator/language/en-GB/en-GB.plg_quickicon_overridecheck.sys.ini
create mode 100644 build/media_src/com_templates/js/admin-template-compare.es6.js
create mode 100644 build/media_src/com_templates/js/admin-template-toggle-switch.es6.js
create mode 100644 build/media_src/plg_quickicon_overridecheck/js/overridecheck.es6.js
create mode 100644 plugins/installer/override/override.php
create mode 100644 plugins/installer/override/override.xml
create mode 100644 plugins/quickicon/overridecheck/overridecheck.php
create mode 100644 plugins/quickicon/overridecheck/overridecheck.xml
diff --git a/administrator/components/com_admin/sql/others/mysql/utf8mb4-conversion-02.sql b/administrator/components/com_admin/sql/others/mysql/utf8mb4-conversion-02.sql
index 4f63f989f60dc..3edcbd3e7978a 100644
--- a/administrator/components/com_admin/sql/others/mysql/utf8mb4-conversion-02.sql
+++ b/administrator/components/com_admin/sql/others/mysql/utf8mb4-conversion-02.sql
@@ -48,7 +48,7 @@ ALTER TABLE `#__users` MODIFY `name` varchar(400) NOT NULL DEFAULT '';
--
-- Step 2.2: Convert all tables to utf8mb4 chracter set with utf8mb4_unicode_ci collation
-- except #__finder_xxx tables, those will have utf8mb4_general_ci collation.
--- Note: The database driver for mysql will change utf8mb4 to utf8 if utf8mb4 is not supported
+-- Note: The database driver for mysql will change utf8mb4 to utf8 if utf8mb4 is not supported
--
ALTER TABLE `#__assets` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
@@ -108,6 +108,7 @@ ALTER TABLE `#__redirect_links` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4
ALTER TABLE `#__schemas` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `#__session` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `#__tags` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+ALTER TABLE `#__template_overrides` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `#__template_styles` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `#__ucm_base` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `#__ucm_content` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
@@ -200,6 +201,7 @@ ALTER TABLE `#__redirect_links` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_un
ALTER TABLE `#__schemas` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `#__session` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `#__tags` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+ALTER TABLE `#__template_overrides` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `#__template_styles` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `#__ucm_base` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `#__ucm_content` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
diff --git a/administrator/components/com_admin/sql/updates/mysql/4.0.0-2018-07-19.sql b/administrator/components/com_admin/sql/updates/mysql/4.0.0-2018-07-19.sql
new file mode 100644
index 0000000000000..93e69b0ef40bb
--- /dev/null
+++ b/administrator/components/com_admin/sql/updates/mysql/4.0.0-2018-07-19.sql
@@ -0,0 +1,18 @@
+CREATE TABLE IF NOT EXISTS `#__template_overrides` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `template` varchar(50) NOT NULL DEFAULT '',
+ `hash_id` varchar(255) NOT NULL DEFAULT '',
+ `extension_id` int(11) DEFAULT 0,
+ `state` tinyint(1) NOT NULL DEFAULT 0,
+ `action` varchar(50) NOT NULL DEFAULT '',
+ `client_id` tinyint(1) unsigned NOT NULL DEFAULT 0,
+ `created_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `modified_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ PRIMARY KEY (`id`),
+ KEY `idx_template` (`template`),
+ KEY `idx_extension_id` (`extension_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
+
+INSERT INTO `#__extensions` (`extension_id`, `package_id`, `name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `manifest_cache`, `params`, `checked_out`, `checked_out_time`, `ordering`, `state`, `namespace`) VALUES
+(491, 0, 'plg_installer_override', 'plugin', 'override', 'installer', 0, 1, 1, 1, '', '', 0, '0000-00-00 00:00:00', 4, 0, ''),
+(492, 0, 'plg_quickicon_overridecheck', 'plugin', 'overridecheck', 'quickicon', 0, 1, 1, 1, '', '', 0, '0000-00-00 00:00:00', 0, 0, ''),
diff --git a/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2018-07-19.sql b/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2018-07-19.sql
new file mode 100644
index 0000000000000..67174b4a65058
--- /dev/null
+++ b/administrator/components/com_admin/sql/updates/postgresql/4.0.0-2018-07-19.sql
@@ -0,0 +1,18 @@
+CREATE TABLE IF NOT EXISTS "#__template_overrides" (
+ "id" serial NOT NULL,
+ "template" varchar(50) DEFAULT '' NOT NULL,
+ "hash_id" varchar(255) DEFAULT '' NOT NULL,
+ "extension_id" bigint DEFAULT 0,
+ "state" smallint DEFAULT 0 NOT NULL,
+ "action" varchar(50) DEFAULT '' NOT NULL,
+ "client_id" smallint DEFAULT 0 NOT NULL,
+ "created_date" timestamp without time zone DEFAULT '1970-01-01 00:00:00' NOT NULL,
+ "modified_date" timestamp without time zone DEFAULT '1970-01-01 00:00:00' NOT NULL,
+ PRIMARY KEY ("id")
+);
+CREATE INDEX "#__template_overrides_idx_template" ON "#__template_overrides" ("template");
+CREATE INDEX "#__template_overrides_idx_extension_id" ON "#__template_overrides" ("extension_id");
+
+INSERT INTO "#__extensions" ("extension_id", "package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "manifest_cache", "params", "checked_out", "checked_out_time", "ordering", "state", "namespace") VALUES
+(491, 0, 'plg_installer_override', 'plugin', 'override', 'installer', 0, 1, 1, 1, '', '', 0, '1970-01-01 00:00:00', 4, 0, ''),
+(492, 0, 'plg_quickicon_overridecheck', 'plugin', 'overridecheck', 'quickicon', 0, 1, 1, 1, '', '', 0, '1970-01-01 00:00:00', 0, 0, '');
diff --git a/administrator/components/com_installer/Model/UpdateModel.php b/administrator/components/com_installer/Model/UpdateModel.php
index 2f3f5c40ab91c..64daf2c894d68 100644
--- a/administrator/components/com_installer/Model/UpdateModel.php
+++ b/administrator/components/com_installer/Model/UpdateModel.php
@@ -404,6 +404,9 @@ public function update($uids, $minimum_stability = Updater::STABILITY_STABLE)
*/
private function install($update)
{
+ // Load overrides plugin.
+ PluginHelper::importPlugin('installer');
+
$app = Factory::getApplication();
if (!isset($update->get('downloadurl')->_data))
diff --git a/administrator/components/com_joomlaupdate/Model/UpdateModel.php b/administrator/components/com_joomlaupdate/Model/UpdateModel.php
index b5d1ae48e08b6..98393e0e114a9 100644
--- a/administrator/components/com_joomlaupdate/Model/UpdateModel.php
+++ b/administrator/components/com_joomlaupdate/Model/UpdateModel.php
@@ -27,6 +27,7 @@
use Joomla\CMS\Log\Log;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Installer\Installer;
+use Joomla\CMS\Plugin\PluginHelper;
/**
* Joomla! update overview Model
@@ -389,7 +390,8 @@ protected function downloadPackage($url, $target)
}
/**
- * Create restoration file.
+ * Create restoration file and trigger onJoomlaBeforeUpdate event, which find the updated core files
+ * which have changed during the update, where there are override for.
*
* @param string $basename Optional base path to the file.
*
@@ -399,11 +401,17 @@ protected function downloadPackage($url, $target)
*/
public function createRestorationFile($basename = null)
{
+ // Load overrides plugin.
+ PluginHelper::importPlugin('installer');
+
// Get a password
$password = UserHelper::genRandomPassword(32);
$app = Factory::getApplication();
$app->setUserState('com_joomlaupdate.password', $password);
+ // Trigger event before joomla update.
+ $app->triggerEvent('onJoomlaBeforeUpdate');
+
// Do we have to use FTP?
$method = Factory::getApplication()->getUserStateFromRequest('com_joomlaupdate.method', 'method', 'direct', 'cmd');
@@ -826,7 +834,8 @@ public function finaliseUpgrade()
}
/**
- * Removes the extracted package file.
+ * Removes the extracted package file and trigger onJoomlaAfterUpdate event, which find the updated core files
+ * which have changed during the update, where there are override for.
*
* @return void
*
@@ -834,11 +843,19 @@ public function finaliseUpgrade()
*/
public function cleanUp()
{
+ // Load overrides plugin.
+ PluginHelper::importPlugin('installer');
+
+ $app = Factory::getApplication();
+
+ // Trigger event after joomla update.
+ $app->triggerEvent('onJoomlaAfterUpdate');
+
// Remove the update package.
$config = Factory::getConfig();
$tempdir = $config->get('tmp_path');
- $file = Factory::getApplication()->getUserState('com_joomlaupdate.file', null);
+ $file = $app->getUserState('com_joomlaupdate.file', null);
$target = $tempdir . '/' . $file;
if (!@unlink($target))
diff --git a/administrator/components/com_templates/Controller/TemplateController.php b/administrator/components/com_templates/Controller/TemplateController.php
index dbe92b27489b6..171f6d0310f57 100644
--- a/administrator/components/com_templates/Controller/TemplateController.php
+++ b/administrator/components/com_templates/Controller/TemplateController.php
@@ -11,14 +11,16 @@
defined('_JEXEC') or die;
+use Joomla\CMS\Client\ClientHelper;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
-use Joomla\Component\Installer\Administrator\Model\InstallModel;
-use Joomla\CMS\Language\Text;
+use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
-use Joomla\CMS\Client\ClientHelper;
-use Joomla\CMS\Factory;
+use Joomla\Component\Installer\Administrator\Model\InstallModel;
+use Joomla\Utilities\ArrayHelper;
/**
* Template style controller class.
@@ -44,6 +46,9 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu
// Apply, Save & New, and Save As copy should be standard on forms.
$this->registerTask('apply', 'save');
+ $this->registerTask('unpublish', 'publish');
+ $this->registerTask('publish', 'publish');
+ $this->registerTask('deleteOverrideHistory', 'publish');
}
/**
@@ -74,6 +79,64 @@ public function close()
$this->setRedirect(Route::_($url, false));
}
+ /**
+ * Marked as Checked/Unchecked of override history.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function publish()
+ {
+ // Check for request forgeries.
+ Session::checkToken() or jexit(Text::_('JINVALID_TOKEN'));
+
+ $app = Factory::getApplication();
+ $file = $this->input->get('file');
+ $id = $this->input->get('id');
+
+ $ids = $this->input->get('cid', array(), 'array');
+ $values = array('publish' => 1, 'unpublish' => 0, 'deleteOverrideHistory' => -3);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($values, $task, 0, 'int');
+
+ if (empty($ids))
+ {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_NO_FILE_SELECTED'), 'warning');
+ }
+ else
+ {
+ /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+
+ // Change the state of the records.
+ if (!$model->publish($ids, $value, $id))
+ {
+ $this->setMessage(implode('
', $model->getErrors()), 'warning');
+ }
+ else
+ {
+ if ($value === 1)
+ {
+ $ntext = 'COM_TEMPLATES_N_OVERRIDE_CHECKED';
+ }
+ elseif ($value === 0)
+ {
+ $ntext = 'COM_TEMPLATES_N_OVERRIDE_UNCHECKED';
+ }
+ elseif ($value === -3)
+ {
+ $ntext = 'COM_TEMPLATES_N_OVERRIDE_DELETED';
+ }
+
+ $this->setMessage(Text::plural($ntext, count($ids)));
+ }
+ }
+
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file;
+ $this->setRedirect(Route::_($url, false));
+ }
+
/**
* Method for copying the template.
*
@@ -738,4 +801,43 @@ public function extractArchive()
$this->setRedirect(Route::_($url, false));
}
}
+
+ /**
+ * Fetch and report updates in \JSON format, for AJAX requests
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function ajax()
+ {
+ $app = $this->app;
+
+ if (!Session::checkToken('get'))
+ {
+ $app->setHeader('status', 403, true);
+ $app->sendHeaders();
+ echo Text::_('JINVALID_TOKEN');
+ $app->close();
+ }
+
+ // Checks status of installer override plugin.
+ if (!PluginHelper::isEnabled('installer', 'override'))
+ {
+ $error = array('installerOverride' => 'disabled');
+
+ echo json_encode($error);
+
+ $app->close();
+ }
+
+ /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+
+ $result = $model->getUpdatedList(true, true);
+
+ echo json_encode($result);
+
+ $app->close();
+ }
}
diff --git a/administrator/components/com_templates/Model/TemplateModel.php b/administrator/components/com_templates/Model/TemplateModel.php
index fe80d7005cfd4..ddabf0cf0b256 100644
--- a/administrator/components/com_templates/Model/TemplateModel.php
+++ b/administrator/components/com_templates/Model/TemplateModel.php
@@ -13,15 +13,16 @@
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
+use Joomla\CMS\Date\Date;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Filesystem\File;
+use Joomla\CMS\Filesystem\Folder;
+use Joomla\CMS\Filesystem\Path;
+use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\FormModel;
use Joomla\CMS\Plugin\PluginHelper;
-use Joomla\Component\Templates\Administrator\Helper\TemplateHelper;
-use Joomla\CMS\Language\Text;
-use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Uri\Uri;
-use Joomla\CMS\Filesystem\Path;
-use Joomla\CMS\Filesystem\Folder;
-use Joomla\CMS\Factory;
+use Joomla\Component\Templates\Administrator\Helper\TemplateHelper;
/**
* Template model class.
@@ -69,6 +70,303 @@ protected function getFile($path, $name)
}
}
+ /**
+ * Method to store file information.
+ *
+ * @param string $path The base path.
+ * @param string $name The file name.
+ * @param stdClass $template The std class object of template.
+ *
+ * @return object StdClass object.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function storeFileInfo($path, $name, $template)
+ {
+ $temp = new \stdClass;
+ $temp->id = base64_encode($path . $name);
+ $temp->client = $template->client_id;
+ $temp->template = $template->element;
+ $temp->extension_id = $template->extension_id;
+
+ if ($coreFile = $this->getCoreFile($path . $name, $template->client_id))
+ {
+ $temp->coreFile = md5_file($coreFile);
+ }
+ else
+ {
+ $temp->coreFile = null;
+ }
+
+ return $temp;
+ }
+
+ /**
+ * Method to get all template list.
+ *
+ * @return object stdClass object
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getTemplateList()
+ {
+ // Get a db connection.
+ $db = Factory::getDbo();
+
+ // Create a new query object.
+ $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'
+ )
+ );
+
+ $query->from($db->quoteName('#__extensions', 'a'))
+ ->where($db->quoteName('a.enabled') . ' = 1')
+ ->where($db->quoteName('a.type') . ' = ' . $db->quote('template'));
+
+ // Reset the query.
+ $db->setQuery($query);
+
+ // Load the results as a list of stdClass objects.
+ $results = $db->loadObjectList();
+
+ return $results;
+ }
+
+ /**
+ * Method to get all updated file list.
+ *
+ * @param boolean $state The optional parameter if you want unchecked list.
+ * @param boolean $all The optional parameter if you want all list.
+ * @param boolean $cleanup The optional parameter if you want to clean record which is no more required.
+ *
+ * @return object stdClass object
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getUpdatedList($state = false, $all = false, $cleanup = false)
+ {
+ // Get a db connection.
+ $db = Factory::getDbo();
+
+ // Create a new query object.
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.template, a.hash_id, a.extension_id, a.state, a.action, a.client_id, a.created_date, a.modified_date'
+ )
+ );
+
+ $template = $this->getTemplate();
+
+ $query->from($db->quoteName('#__template_overrides', 'a'));
+
+ if (!$all)
+ {
+ $query->where('extension_id = ' . $db->quote($template->extension_id));
+ }
+
+ if ($state)
+ {
+ $query->where('state = 0');
+ }
+
+ $query->order($db->quoteName('a.modified_date') . ' DESC');
+
+ // Reset the query.
+ $db->setQuery($query);
+
+ // Load the results as a list of stdClass objects.
+ $pks = $db->loadObjectList();
+
+ if ($state)
+ {
+ return $pks;
+ }
+
+ $results = array();
+
+ foreach ($pks as $pk)
+ {
+ $client = ApplicationHelper::getClientInfo($pk->client_id);
+ $path = Path::clean($client->path . '/templates/' . $pk->template . base64_decode($pk->hash_id));
+
+ if (file_exists($path))
+ {
+ $results[] = $pk;
+ }
+ elseif ($cleanup)
+ {
+ $cleanupIds = array();
+ $cleanupIds[] = $pk->hash_id;
+ $this->publish($cleanupIds, -3, $pk->extension_id);
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Method to get a list of all the core files of override files.
+ *
+ * @return array A array of all core files.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getCoreList()
+ {
+ // Get list of all templates
+ $templates = $this->getTemplateList();
+
+ // Initialize the array variable to store core file list.
+ $this->coreFileList = array();
+
+ $app = Factory::getApplication();
+
+ foreach ($templates as $template)
+ {
+ $client = ApplicationHelper::getClientInfo($template->client_id);
+ $element = Path::clean($client->path . '/templates/' . $template->element . '/');
+ $path = Path::clean($element . 'html/');
+
+ if (is_dir($path))
+ {
+ $this->prepareCoreFiles($path, $element, $template);
+ }
+ else
+ {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_TEMPLATE_FOLDER_NOT_FOUND'), 'error');
+
+ return false;
+ }
+ }
+
+ // Sort list of stdClass array.
+ usort(
+ $this->coreFileList,
+ function ($a, $b)
+ {
+ return strcmp($a->id, $b->id);
+ }
+ );
+
+ return $this->coreFileList;
+ }
+
+ /**
+ * Prepare core files.
+ *
+ * @param string $dir The path of the directory to scan.
+ * @param string $element The path of the template element.
+ * @param stdClass $template The stdClass object of template.
+ *
+ * @return array
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function prepareCoreFiles($dir, $element, $template)
+ {
+ $dirFiles = scandir($dir);
+
+ foreach ($dirFiles as $key => $value)
+ {
+ if (in_array($value, array('.', '..', 'node_modules')))
+ {
+ continue;
+ }
+
+ if (is_dir($dir . $value))
+ {
+ $relativePath = str_replace($element, '', $dir . $value);
+ $this->prepareCoreFiles($dir . $value . '/', $element, $template);
+ }
+ else
+ {
+ $ext = pathinfo($dir . $value, PATHINFO_EXTENSION);
+ $allowedFormat = $this->checkFormat($ext);
+
+ if ($allowedFormat === true)
+ {
+ $relativePath = str_replace($element, '', $dir);
+ $info = $this->storeFileInfo('/' . $relativePath, $value, $template);
+
+ if ($info)
+ {
+ $this->coreFileList[] = $info;
+ }
+ }
+ }
+ }
+
+ return;
+ }
+
+ /**
+ * Method to update status of list.
+ *
+ * @param array $ids The base path.
+ * @param array $value The file name.
+ * @param integer $exid The template extension id.
+ *
+ * @return integer Number of files changed.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function publish($ids, $value, $exid)
+ {
+ $db = Factory::getDbo();
+
+ foreach ($ids as $id)
+ {
+ if ($value === -3)
+ {
+ $deleteQuery = $db->getQuery(true)
+ ->delete($db->quoteName('#__template_overrides'))
+ ->where($db->quoteName('hash_id') . ' = ' . $db->quote($id))
+ ->where($db->quoteName('extension_id') . ' = ' . $db->quote($exid));
+
+ try
+ {
+ // Set the query using our newly populated query object and execute it.
+ $db->setQuery($deleteQuery);
+ $result = $db->execute();
+ }
+ catch (\RuntimeException $e)
+ {
+ return $e;
+ }
+ }
+ elseif ($value === 1 || $value === 0)
+ {
+ $updateQuery = $db->getQuery(true)
+ ->update($db->quoteName('#__template_overrides'))
+ ->set($db->quoteName('state') . ' = ' . $db->quote($value))
+ ->where($db->quoteName('hash_id') . ' = ' . $db->quote($id))
+ ->where($db->quoteName('extension_id') . ' = ' . $db->quote($exid));
+
+ try
+ {
+ // Set the query using our newly populated query object and execute it.
+ $db->setQuery($updateQuery);
+ $result = $db->execute();
+ }
+ catch (\RuntimeException $e)
+ {
+ return $e;
+ }
+ }
+ }
+
+ return $result;
+ }
+
/**
* Method to get a list of all the files to edit in a template.
*
@@ -107,6 +405,9 @@ public function getFiles()
return false;
}
+
+ // Clean up override history
+ $this->getUpdatedList(false, true, true);
}
return $result;
@@ -154,6 +455,148 @@ public function getDirectoryTree($dir)
return $result;
}
+ /**
+ * Method to get the core file of override file
+ *
+ * @param string $file Override file
+ * @param integer $client_id Client Id
+ *
+ * @return string $corefile The full path and file name for the target file, or boolean false if the file is not found in any of the paths.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getCoreFile($file, $client_id)
+ {
+ $app = Factory::getApplication();
+ $filePath = Path::clean($file);
+ $explodeArray = explode(DIRECTORY_SEPARATOR, $filePath);
+
+ // Only allow html/ folder
+ if ($explodeArray['1'] !== 'html')
+ {
+ return false;
+ }
+
+ $fileName = basename($filePath);
+ $type = $explodeArray['2'];
+ $client = ApplicationHelper::getClientInfo($client_id);
+
+ $componentPath = Path::clean($client->path . '/components/');
+ $modulePath = Path::clean($client->path . '/modules/');
+ $layoutPath = Path::clean(JPATH_ROOT . '/layouts/');
+
+ // For modules
+ if (stristr($type, 'mod_') !== false)
+ {
+ $folder = $explodeArray['2'];
+ $htmlPath = Path::clean($modulePath . $folder . '/tmpl/');
+ $fileName = $this->getSafeName($fileName);
+ $coreFile = Path::find($htmlPath, $fileName);
+
+ return $coreFile;
+ }
+ elseif (stristr($type, 'com_') !== false)
+ {
+ // For components
+ $folder = $explodeArray['2'];
+ $subFolder = $explodeArray['3'];
+ $fileName = $this->getSafeName($fileName);
+
+ // The new scheme, if a view has a tmpl folder
+ $newHtmlPath = Path::clean($componentPath . $folder . '/tmpl/' . $subFolder . '/');
+
+ if (!$coreFile = Path::find($newHtmlPath, $fileName))
+ {
+ // The old scheme, the views are directly in the component/tmpl folder
+ $oldHtmlPath = Path::clean($componentPath . $folder . '/views/' . $subFolder . '/tmpl/');
+ $coreFile = Path::find($oldHtmlPath, $fileName);
+
+ return $coreFile;
+ }
+
+ return $coreFile;
+ }
+ elseif (stristr($type, 'layouts') !== false)
+ {
+ // For Jlayouts
+ $subtype = $explodeArray['3'];
+
+ if (stristr($subtype, 'com_'))
+ {
+ $folder = $explodeArray['3'];
+ $subFolder = array_slice($explodeArray, 4, -1);
+ $subFolder = implode(DIRECTORY_SEPARATOR, $subFolder);
+ $htmlPath = Path::clean($componentPath . $folder . '/layouts/' . $subFolder);
+ $fileName = $this->getSafeName($fileName);
+ $coreFile = Path::find($htmlPath, $fileName);
+
+ return $coreFile;
+ }
+ elseif (stristr($subtype, 'joomla') || stristr($subtype, 'libraries') || stristr($subtype, 'plugins'))
+ {
+ $subFolder = array_slice($explodeArray, 3, -1);
+ $subFolder = implode(DIRECTORY_SEPARATOR, $subFolder);
+ $htmlPath = Path::clean($layoutPath . $subFolder);
+ $fileName = $this->getSafeName($fileName);
+ $coreFile = Path::find($htmlPath, $fileName);
+
+ return $coreFile;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates a safe file name for the given name.
+ *
+ * @param string $name The filename
+ *
+ * @return string $fileName The filtered name without Date
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function getSafeName($name)
+ {
+ if (preg_match('/[0-9]/', $name))
+ {
+ // Get the extension
+ $extension = File::getExt($name);
+
+ // Remove ( Date ) from file
+ $explodeArray = explode('-', $name);
+ $size = count($explodeArray);
+ $date = $explodeArray[$size - 2] . '-' . str_replace('.' . $extension, '', $explodeArray[$size - 1]);
+
+ if ($this->validateDate($date))
+ {
+ $nameWithoutExtension = implode('-', array_slice($explodeArray, 0, -2));
+
+ // Filtered name
+ $name = $nameWithoutExtension . '.' . $extension;
+ }
+ }
+
+ return $name;
+ }
+
+ /**
+ * Validate Date in file name.
+ *
+ * @param string $date Date to validate.
+ *
+ * @return boolean Return true if date is valid and false if not.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function validateDate($date)
+ {
+ $format = 'Ymd-His';
+ $valid = Date::createFromFormat($format, $date);
+
+ return $valid && $valid->format($format) === $date;
+ }
+
/**
* Method to auto-populate the model state.
*
@@ -457,8 +900,15 @@ public function &getSource()
if (file_exists($filePath))
{
$item->extension_id = $this->getState('extension.id');
- $item->filename = $fileName;
+ $item->filename = Path::clean($fileName);
$item->source = file_get_contents($filePath);
+ $item->filePath = Path::clean($filePath);
+
+ if ($coreFile = $this->getCoreFile($fileName, $this->template->client_id))
+ {
+ $item->coreFile = $coreFile;
+ $item->core = file_get_contents($coreFile);
+ }
}
else
{
diff --git a/administrator/components/com_templates/Model/TemplatesModel.php b/administrator/components/com_templates/Model/TemplatesModel.php
index da83eaea09512..bcb54866fa55a 100644
--- a/administrator/components/com_templates/Model/TemplatesModel.php
+++ b/administrator/components/com_templates/Model/TemplatesModel.php
@@ -13,8 +13,9 @@
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
-use Joomla\CMS\MVC\Model\ListModel;
+use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
+use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Component\Templates\Administrator\Helper\TemplatesHelper;
/**
@@ -68,11 +69,51 @@ public function getItems()
{
$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 __DEPLOY_VERSION__
+ */
+ public function updated($exid)
+ {
+ $db = Factory::getDbo();
+
+ // Select the required fields from the table
+ $query = $db->getQuery(true)
+ ->select('a.template')
+ ->from($db->quoteName('#__template_overrides', 'a'))
+ ->where('extension_id = ' . $db->quote($exid))
+ ->where('state = 0');
+
+ // 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.
*
diff --git a/administrator/components/com_templates/View/Template/HtmlView.php b/administrator/components/com_templates/View/Template/HtmlView.php
index ee7339b2e5ac2..adde38c1eaf31 100644
--- a/administrator/components/com_templates/View/Template/HtmlView.php
+++ b/administrator/components/com_templates/View/Template/HtmlView.php
@@ -12,13 +12,14 @@
defined('_JEXEC') or die;
use Joomla\CMS\Component\ComponentHelper;
-use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Language\Text;
+use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
+use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Uri\Uri;
-use Joomla\CMS\Factory;
-use Joomla\CMS\Filter\InputFilter;
/**
* View to edit a template style.
@@ -123,6 +124,15 @@ class HtmlView extends BaseHtmlView
*/
protected $archive;
+ /**
+ * The state of installer override plugin.
+ *
+ * @var array
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected $pluginState;
+
/**
* Execute and display a template script.
*
@@ -141,6 +151,8 @@ public function display($tpl = null)
$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');
$params = ComponentHelper::getParams('com_templates');
$imageTypes = explode(',', $params->get('image_formats'));
@@ -277,6 +289,11 @@ protected function addToolbar()
}
}
+ if (count($this->updatedList) !== 0 && $this->pluginState)
+ {
+ ToolbarHelper::custom('template.deleteOverrideHistory', 'delete', 'move', 'COM_TEMPLATES_BUTTON_DELETE_LIST_ENTRY', true, 'updateForm');
+ }
+
if ($this->type == 'home')
{
ToolbarHelper::cancel('template.cancel', 'JTOOLBAR_CLOSE');
diff --git a/administrator/components/com_templates/View/Templates/HtmlView.php b/administrator/components/com_templates/View/Templates/HtmlView.php
index 6cbe943572c22..1214cfbd867aa 100644
--- a/administrator/components/com_templates/View/Templates/HtmlView.php
+++ b/administrator/components/com_templates/View/Templates/HtmlView.php
@@ -13,10 +13,11 @@
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Helper\ContentHelper;
-use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
-use Joomla\Component\Templates\Administrator\Helper\TemplatesHelper;
use Joomla\CMS\Language\Text;
+use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
+use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Toolbar\ToolbarHelper;
+use Joomla\Component\Templates\Administrator\Helper\TemplatesHelper;
/**
* View class for a list of template styles.
@@ -79,6 +80,15 @@ class HtmlView extends BaseHtmlView
*/
public $preview;
+ /**
+ * The state of installer override plugin.
+ *
+ * @var array
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected $pluginState;
+
/**
* Execute and display a template script.
*
@@ -98,6 +108,7 @@ public function display($tpl = null)
$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');
TemplatesHelper::addSubmenu('templates');
diff --git a/administrator/components/com_templates/forms/source.xml b/administrator/components/com_templates/forms/source.xml
index 823841544041f..c6493937c37e7 100644
--- a/administrator/components/com_templates/forms/source.xml
+++ b/administrator/components/com_templates/forms/source.xml
@@ -3,7 +3,7 @@
source->filename, $this->template->element); ?>
@@ -70,6 +83,14 @@