diff --git a/src/script.php b/src/script.php index 8fcfb36..cdcabbe 100644 --- a/src/script.php +++ b/src/script.php @@ -1,6 +1,6 @@ + * Copyright (C) 2026 Moko Consulting * * This file is part of a Moko Consulting project. * @@ -12,8 +12,9 @@ * Joomla calls the methods in this class automatically during template * install, update, and uninstall via the element in * templateDetails.xml. - * Joomla 5 and 6 compatible — uses the InstallerScriptInterface when - * available, falls back to the legacy class-based approach otherwise. + * + * On first install, detects MokoCassiopeia and migrates template styles, + * parameters, menu assignments, and user files automatically. */ defined('_JEXEC') or die; @@ -24,33 +25,19 @@ use Joomla\CMS\Log\Log; class Tpl_MokoonyxInstallerScript { - /** - * Minimum PHP version required by this template. - */ private const MIN_PHP = '8.1.0'; - - /** - * Minimum Joomla version required by this template. - */ private const MIN_JOOMLA = '4.4.0'; - /** - * Called before install/update/uninstall. - * - * @param string $type install, update, discover_install, or uninstall. - * @param InstallerAdapter $parent The adapter calling this method. - * - * @return bool True to proceed, false to abort. - */ + private const OLD_NAME = 'mokocassiopeia'; + private const NEW_NAME = 'mokoonyx'; + private const OLD_DISPLAY = 'MokoCassiopeia'; + private const NEW_DISPLAY = 'MokoOnyx'; + public function preflight(string $type, InstallerAdapter $parent): bool { if (version_compare(PHP_VERSION, self::MIN_PHP, '<')) { Factory::getApplication()->enqueueMessage( - sprintf( - 'MokoOnyx requires PHP %s or later. You are running PHP %s.', - self::MIN_PHP, - PHP_VERSION - ), + sprintf('MokoOnyx requires PHP %s or later. You are running PHP %s.', self::MIN_PHP, PHP_VERSION), 'error' ); return false; @@ -58,11 +45,7 @@ class Tpl_MokoonyxInstallerScript if (version_compare(JVERSION, self::MIN_JOOMLA, '<')) { Factory::getApplication()->enqueueMessage( - sprintf( - 'MokoOnyx requires Joomla %s or later. You are running Joomla %s.', - self::MIN_JOOMLA, - JVERSION - ), + sprintf('MokoOnyx requires Joomla %s or later. You are running Joomla %s.', self::MIN_JOOMLA, JVERSION), 'error' ); return false; @@ -71,37 +54,17 @@ class Tpl_MokoonyxInstallerScript return true; } - /** - * Called after a successful install. - * - * @param InstallerAdapter $parent The adapter calling this method. - * - * @return bool - */ public function install(InstallerAdapter $parent): bool { $this->logMessage('MokoOnyx template installed.'); return true; } - /** - * Called after a successful update. - * - * This is where the CSS variable sync runs — it detects variables that - * were added in the new version and injects them into the user's custom - * palette files without overwriting existing values. - * - * @param InstallerAdapter $parent The adapter calling this method. - * - * @return bool - */ public function update(InstallerAdapter $parent): bool { $this->logMessage('MokoOnyx template updated.'); - // Run CSS variable sync to inject any new variables into user's custom palettes. $synced = $this->syncCustomVariables($parent); - if ($synced > 0) { Factory::getApplication()->enqueueMessage( sprintf( @@ -116,47 +79,165 @@ class Tpl_MokoonyxInstallerScript return true; } - /** - * Called after a successful uninstall. - * - * @param InstallerAdapter $parent The adapter calling this method. - * - * @return bool - */ public function uninstall(InstallerAdapter $parent): bool { $this->logMessage('MokoOnyx template uninstalled.'); return true; } - /** - * Called after install/update completes (regardless of type). - * - * @param string $type install, update, or discover_install. - * @param InstallerAdapter $parent The adapter calling this method. - * - * @return bool - */ public function postflight(string $type, InstallerAdapter $parent): bool { + // On install: migrate from MokoCassiopeia if it exists + if ($type === 'install') { + $this->migrateFromCassiopeia(); + } + return true; } /** - * Run the CSS variable sync utility. - * - * Loads sync_custom_vars.php from the template directory and calls - * MokoCssVarSync::run() to detect and inject missing variables. - * - * @param InstallerAdapter $parent The adapter calling this method. - * - * @return int Number of variables added across all files. + * Detect MokoCassiopeia and migrate styles, params, and user files to MokoOnyx. */ + private function migrateFromCassiopeia(): void + { + $db = Factory::getDbo(); + + // Check if MokoCassiopeia has any template styles + $query = $db->getQuery(true) + ->select('*') + ->from('#__template_styles') + ->where($db->quoteName('template') . ' = ' . $db->quote(self::OLD_NAME)) + ->where($db->quoteName('client_id') . ' = 0'); + $oldStyles = $db->setQuery($query)->loadObjectList(); + + if (empty($oldStyles)) { + $this->logMessage('No MokoCassiopeia styles found — fresh install.'); + return; + } + + $this->logMessage('MokoCassiopeia detected — migrating ' . count($oldStyles) . ' style(s).'); + + // 1. Copy template styles with params + foreach ($oldStyles as $oldStyle) { + $newTitle = str_replace(self::OLD_DISPLAY, self::NEW_DISPLAY, $oldStyle->title); + $newTitle = str_replace(self::OLD_NAME, self::NEW_NAME, $newTitle); + + // Check if MokoOnyx already has a style with this title + $check = $db->getQuery(true) + ->select('COUNT(*)') + ->from('#__template_styles') + ->where($db->quoteName('template') . ' = ' . $db->quote(self::NEW_NAME)) + ->where($db->quoteName('title') . ' = ' . $db->quote($newTitle)); + if ((int) $db->setQuery($check)->loadResult() > 0) { + // Update existing MokoOnyx style with MokoCassiopeia's params + $params = is_string($oldStyle->params) + ? str_replace(self::OLD_NAME, self::NEW_NAME, $oldStyle->params) + : $oldStyle->params; + + $update = $db->getQuery(true) + ->update('#__template_styles') + ->set($db->quoteName('params') . ' = ' . $db->quote($params)) + ->where($db->quoteName('template') . ' = ' . $db->quote(self::NEW_NAME)) + ->where($db->quoteName('title') . ' = ' . $db->quote($newTitle)); + $db->setQuery($update)->execute(); + $this->logMessage("Updated existing MokoOnyx style: {$newTitle}"); + continue; + } + + // Create new MokoOnyx style from MokoCassiopeia style + $newStyle = clone $oldStyle; + unset($newStyle->id); + $newStyle->template = self::NEW_NAME; + $newStyle->title = $newTitle; + $newStyle->home = 0; // Don't set as default yet + + if (is_string($newStyle->params)) { + $newStyle->params = str_replace(self::OLD_NAME, self::NEW_NAME, $newStyle->params); + } + + $db->insertObject('#__template_styles', $newStyle, 'id'); + $newId = $newStyle->id; + + // If the old style was the default, make the new one default + if ($oldStyle->home == 1) { + $db->setQuery( + $db->getQuery(true) + ->update('#__template_styles') + ->set($db->quoteName('home') . ' = 1') + ->where('id = ' . (int) $newId) + )->execute(); + + $db->setQuery( + $db->getQuery(true) + ->update('#__template_styles') + ->set($db->quoteName('home') . ' = 0') + ->where('id = ' . (int) $oldStyle->id) + )->execute(); + + $this->logMessage('Set MokoOnyx as default site template.'); + } + + $this->logMessage("Migrated style: {$oldStyle->title} → {$newTitle}"); + } + + // 2. Copy user files (custom themes, user.css, user.js) + $this->copyUserFiles(); + + // 3. Notify admin + Factory::getApplication()->enqueueMessage( + 'MokoOnyx has been installed as a replacement for MokoCassiopeia.
' + . 'Your template settings and custom files have been migrated automatically. ' + . 'MokoOnyx is now your active site template. ' + . 'You can safely uninstall MokoCassiopeia from Extensions → Manage.', + 'success' + ); + } + + /** + * Copy user-specific files from MokoCassiopeia media to MokoOnyx media. + */ + private function copyUserFiles(): void + { + $oldMedia = JPATH_ROOT . '/media/templates/site/' . self::OLD_NAME; + $newMedia = JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME; + + if (!is_dir($oldMedia) || !is_dir($newMedia)) { + return; + } + + $userFiles = [ + 'css/theme/light.custom.css', + 'css/theme/dark.custom.css', + 'css/theme/light.custom.min.css', + 'css/theme/dark.custom.min.css', + 'css/user.css', + 'css/user.min.css', + 'js/user.js', + 'js/user.min.js', + ]; + + $copied = 0; + foreach ($userFiles as $relPath) { + $src = $oldMedia . '/' . $relPath; + $dst = $newMedia . '/' . $relPath; + if (is_file($src) && !is_file($dst)) { + $dstDir = dirname($dst); + if (!is_dir($dstDir)) { + mkdir($dstDir, 0755, true); + } + copy($src, $dst); + $copied++; + } + } + + if ($copied > 0) { + $this->logMessage("Copied {$copied} user file(s) from MokoCassiopeia."); + } + } + private function syncCustomVariables(InstallerAdapter $parent): int { $templateDir = $parent->getParent()->getPath('source'); - - // The sync script lives alongside this script in the template root. $syncScript = $templateDir . '/sync_custom_vars.php'; if (!is_file($syncScript)) { @@ -172,20 +253,13 @@ class Tpl_MokoonyxInstallerScript } try { - $joomlaRoot = JPATH_ROOT; - $results = MokoCssVarSync::run($joomlaRoot); + $results = MokoCssVarSync::run(JPATH_ROOT); $totalAdded = 0; foreach ($results as $filePath => $result) { $totalAdded += count($result['added']); if (!empty($result['added'])) { - $this->logMessage( - sprintf( - 'CSS sync: added %d variable(s) to %s', - count($result['added']), - basename($filePath) - ) - ); + $this->logMessage(sprintf('CSS sync: added %d variable(s) to %s', count($result['added']), basename($filePath))); } } @@ -196,12 +270,6 @@ class Tpl_MokoonyxInstallerScript } } - /** - * Log a message to Joomla's log system. - * - * @param string $message The log message. - * @param string $priority Log priority (info, warning, error). - */ private function logMessage(string $message, string $priority = 'info'): void { $priorities = [