feat: index.php bootstrap migration from MokoCassiopeia
Joomla 6 doesn't call <scriptfile> for templates. Instead, run migration on first page load via index.php: - Detect MokoCassiopeia styles → copy params to MokoOnyx - Create matching style copies for additional styles - Set MokoOnyx as default if Cassiopeia was default - Copy user files (custom themes, user.css, user.js) - Redirect update server to MokoOnyx - Creates .migrated marker so it only runs once Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
205
src/helper/migrate.php
Normal file
205
src/helper/migrate.php
Normal file
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* 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(
|
||||
'<strong>MokoOnyx has imported your MokoCassiopeia settings.</strong><br>'
|
||||
. 'You can safely uninstall MokoCassiopeia from Extensions → Manage.',
|
||||
'success'
|
||||
);
|
||||
}
|
||||
})();
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user