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;
|
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\Factory;
|
||||||
use Joomla\CMS\HTML\HTMLHelper;
|
use Joomla\CMS\HTML\HTMLHelper;
|
||||||
use Joomla\CMS\Language\Text;
|
use Joomla\CMS\Language\Text;
|
||||||
|
|||||||
Reference in New Issue
Block a user