diff --git a/src/helper/migrate.php b/src/helper/migrate.php new file mode 100644 index 0000000..8d94ecc --- /dev/null +++ b/src/helper/migrate.php @@ -0,0 +1,205 @@ + + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * One-time migration from MokoCassiopeia → MokoOnyx. + * Called from index.php on first page load. Creates a .migrated + * marker file so it only runs once. + */ + +defined('_JEXEC') or die; + +use Joomla\CMS\Factory; +use Joomla\CMS\Log\Log; + +(function () { + $markerFile = __DIR__ . '/../.migrated'; + + // Already migrated + if (file_exists($markerFile)) { + return; + } + + $db = Factory::getDbo(); + $app = Factory::getApplication(); + + $oldName = 'mokocassiopeia'; + $newName = 'mokoonyx'; + $oldDisplay = 'MokoCassiopeia'; + $newDisplay = 'MokoOnyx'; + + // Init logger + Log::addLogger( + ['text_file' => 'mokoonyx_migrate.log.php'], + Log::ALL, + ['mokoonyx_migrate'] + ); + + $log = function (string $msg, int $level = Log::INFO) { + Log::add($msg, $level, 'mokoonyx_migrate'); + }; + + $log('=== MokoOnyx migration started (index.php bootstrap) ==='); + + // Check if MokoCassiopeia has styles to migrate + $query = $db->getQuery(true) + ->select('*') + ->from('#__template_styles') + ->where($db->quoteName('template') . ' = ' . $db->quote($oldName)) + ->where($db->quoteName('client_id') . ' = 0'); + $oldStyles = $db->setQuery($query)->loadObjectList(); + + if (empty($oldStyles)) { + $log('No MokoCassiopeia styles found — fresh install, nothing to migrate.'); + @file_put_contents($markerFile, date('Y-m-d H:i:s') . ' fresh install'); + return; + } + + $log('Found ' . count($oldStyles) . ' MokoCassiopeia style(s) to migrate.'); + + // Get the default MokoOnyx style (created by Joomla installer) + $query = $db->getQuery(true) + ->select('id') + ->from('#__template_styles') + ->where($db->quoteName('template') . ' = ' . $db->quote($newName)) + ->where($db->quoteName('client_id') . ' = 0') + ->order($db->quoteName('id') . ' ASC'); + $defaultOnyxId = (int) $db->setQuery($query, 0, 1)->loadResult(); + + $isFirst = true; + + foreach ($oldStyles as $old) { + $newTitle = str_replace($oldDisplay, $newDisplay, $old->title); + $newTitle = str_replace($oldName, $newName, $newTitle); + $newParams = is_string($old->params) + ? str_replace($oldName, $newName, $old->params) + : $old->params; + + if ($isFirst && $defaultOnyxId) { + // Apply params to the installer-created default MokoOnyx style + $update = $db->getQuery(true) + ->update('#__template_styles') + ->set($db->quoteName('title') . ' = ' . $db->quote($newTitle)) + ->set($db->quoteName('params') . ' = ' . $db->quote($newParams)) + ->where('id = ' . $defaultOnyxId); + $db->setQuery($update)->execute(); + + // Set as default if MokoCassiopeia was default + if ($old->home == 1) { + $db->setQuery( + $db->getQuery(true) + ->update('#__template_styles') + ->set($db->quoteName('home') . ' = 1') + ->where('id = ' . $defaultOnyxId) + )->execute(); + + $db->setQuery( + $db->getQuery(true) + ->update('#__template_styles') + ->set($db->quoteName('home') . ' = 0') + ->where('id = ' . (int) $old->id) + )->execute(); + + $log('Set MokoOnyx as default site template.'); + } + + $log("Applied params to default MokoOnyx style: {$newTitle}"); + $isFirst = false; + continue; + } + + // Additional styles: check if already exists + $check = $db->getQuery(true) + ->select('COUNT(*)') + ->from('#__template_styles') + ->where($db->quoteName('template') . ' = ' . $db->quote($newName)) + ->where($db->quoteName('title') . ' = ' . $db->quote($newTitle)); + if ((int) $db->setQuery($check)->loadResult() > 0) { + $log("Style '{$newTitle}' already exists — skipping."); + continue; + } + + // Create new MokoOnyx style copy + $new = clone $old; + unset($new->id); + $new->template = $newName; + $new->title = $newTitle; + $new->params = $newParams; + $new->home = 0; + + try { + $db->insertObject('#__template_styles', $new, 'id'); + $log("Created MokoOnyx style: {$newTitle}"); + } catch (\Throwable $e) { + $log("Failed to create style '{$newTitle}': " . $e->getMessage(), Log::WARNING); + } + } + + // Copy user files from MokoCassiopeia media + $oldMedia = JPATH_ROOT . '/media/templates/site/' . $oldName; + $newMedia = JPATH_ROOT . '/media/templates/site/' . $newName; + + if (is_dir($oldMedia) && is_dir($newMedia)) { + $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 $rel) { + $src = $oldMedia . '/' . $rel; + $dst = $newMedia . '/' . $rel; + if (is_file($src) && !is_file($dst)) { + $dir = dirname($dst); + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + copy($src, $dst); + $copied++; + } + } + + if ($copied > 0) { + $log("Copied {$copied} user file(s) from MokoCassiopeia."); + } + } + + // Update the update server + try { + $onyxUpdatesUrl = 'https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/updates.xml'; + $query = $db->getQuery(true) + ->update('#__update_sites') + ->set($db->quoteName('location') . ' = ' . $db->quote($onyxUpdatesUrl)) + ->set($db->quoteName('name') . ' = ' . $db->quote($newDisplay)) + ->where($db->quoteName('location') . ' LIKE ' . $db->quote('%MokoCassiopeia%')); + $db->setQuery($query)->execute(); + $n = $db->getAffectedRows(); + if ($n > 0) { + $log("Redirected {$n} update site(s) to MokoOnyx."); + } + } catch (\Throwable $e) { + $log('Update server redirect failed: ' . $e->getMessage(), Log::WARNING); + } + + // Write marker file + @file_put_contents($markerFile, date('Y-m-d H:i:s') . " migrated {$oldName} → {$newName}"); + + $log('=== Migration completed ==='); + + // Enqueue message for admin + if ($app->isClient('administrator')) { + $app->enqueueMessage( + 'MokoOnyx has imported your MokoCassiopeia settings.
' + . 'You can safely uninstall MokoCassiopeia from Extensions → Manage.', + 'success' + ); + } +})(); diff --git a/src/index.php b/src/index.php index 5de0bc1..add7e31 100644 --- a/src/index.php +++ b/src/index.php @@ -9,6 +9,11 @@ defined('_JEXEC') or die; +// One-time migration from MokoCassiopeia (runs once, creates .migrated marker) +if (!file_exists(__DIR__ . '/.migrated')) { + require_once __DIR__ . '/helper/migrate.php'; +} + use Joomla\CMS\Factory; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Text;