Bridge: embed in script.php, rename-in-place (no downloads)
Rewrote bridge from scratch as part of script.php postflight(): 1. Rename templates/mokocassiopeia → mokoonyx 2. Rename media/templates/site/mokocassiopeia → mokoonyx 3. Update #__extensions element + name 4. Update all #__template_styles (template, title, params) 5. Redirect #__update_sites to MokoOnyx updates.xml 6. Clear #__updates cache No HTTP requests, no ZIP downloads, no separate bridge.php file. Reverted updates.xml download URLs back to MokoCassiopeia releases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,256 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bridge migration helper — MokoCassiopeia → MokoOnyx
|
||||
*
|
||||
* Renames template files/folders and updates the database to migrate
|
||||
* from MokoCassiopeia to MokoOnyx. No external downloads required.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Log\Log;
|
||||
|
||||
class MokoBridgeMigration
|
||||
{
|
||||
private const OLD_NAME = 'mokocassiopeia';
|
||||
private const NEW_NAME = 'mokoonyx';
|
||||
|
||||
private const OLD_DISPLAY = 'MokoCassiopeia';
|
||||
private const NEW_DISPLAY = 'MokoOnyx';
|
||||
|
||||
/**
|
||||
* Run the full migration.
|
||||
*/
|
||||
public static function run(): bool
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
// Already migrated?
|
||||
if (is_dir(JPATH_ROOT . '/templates/' . self::NEW_NAME)) {
|
||||
self::log('MokoOnyx template dir already exists — updating database only.');
|
||||
self::updateDatabase();
|
||||
self::notifyUser($app);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 1. Rename template directory
|
||||
$renamed = self::renameTemplateDir();
|
||||
if (!$renamed) {
|
||||
$app->enqueueMessage(
|
||||
'MokoOnyx migration: could not rename template directory. '
|
||||
. 'Please rename templates/mokocassiopeia to templates/mokoonyx manually.',
|
||||
'warning'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Rename media directory
|
||||
self::renameMediaDir();
|
||||
|
||||
// 3. Update database (extensions, template_styles, menu assignments)
|
||||
self::updateDatabase();
|
||||
|
||||
// 4. Notify admin
|
||||
self::notifyUser($app);
|
||||
|
||||
self::log('Bridge migration completed successfully.');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename templates/mokocassiopeia → templates/mokoonyx
|
||||
*/
|
||||
private static function renameTemplateDir(): bool
|
||||
{
|
||||
$oldDir = JPATH_ROOT . '/templates/' . self::OLD_NAME;
|
||||
$newDir = JPATH_ROOT . '/templates/' . self::NEW_NAME;
|
||||
|
||||
if (!is_dir($oldDir)) {
|
||||
self::log('Bridge: old template dir not found: ' . $oldDir, 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_dir($newDir)) {
|
||||
self::log('Bridge: new template dir already exists — skipping rename');
|
||||
return true;
|
||||
}
|
||||
|
||||
$result = @rename($oldDir, $newDir);
|
||||
if ($result) {
|
||||
self::log('Bridge: renamed template dir to ' . self::NEW_NAME);
|
||||
} else {
|
||||
self::log('Bridge: failed to rename template dir', 'error');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename media/templates/site/mokocassiopeia → mokoonyx
|
||||
*/
|
||||
private static function renameMediaDir(): void
|
||||
{
|
||||
$oldMedia = JPATH_ROOT . '/media/templates/site/' . self::OLD_NAME;
|
||||
$newMedia = JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME;
|
||||
|
||||
if (!is_dir($oldMedia)) {
|
||||
self::log('Bridge: old media dir not found — skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_dir($newMedia)) {
|
||||
self::log('Bridge: new media dir already exists — skipping rename');
|
||||
return;
|
||||
}
|
||||
|
||||
if (@rename($oldMedia, $newMedia)) {
|
||||
self::log('Bridge: renamed media dir to ' . self::NEW_NAME);
|
||||
} else {
|
||||
self::log('Bridge: failed to rename media dir', 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all database references from mokocassiopeia → mokoonyx.
|
||||
*/
|
||||
private static function updateDatabase(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// 1. Update #__extensions — change element and name
|
||||
$query = $db->getQuery(true)
|
||||
->update('#__extensions')
|
||||
->set($db->quoteName('element') . ' = ' . $db->quote(self::NEW_NAME))
|
||||
->set($db->quoteName('name') . ' = ' . $db->quote(self::NEW_NAME))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote(self::OLD_NAME))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('template'));
|
||||
try {
|
||||
$db->setQuery($query)->execute();
|
||||
$affected = $db->getAffectedRows();
|
||||
if ($affected > 0) {
|
||||
self::log("Bridge: updated {$affected} row(s) in #__extensions");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
self::log('Bridge: #__extensions update failed: ' . $e->getMessage(), 'error');
|
||||
}
|
||||
|
||||
// 2. Update #__template_styles — rename template and title
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from('#__template_styles')
|
||||
->where($db->quoteName('template') . ' = ' . $db->quote(self::OLD_NAME));
|
||||
$styles = $db->setQuery($query)->loadObjectList();
|
||||
|
||||
foreach ($styles as $style) {
|
||||
$newTitle = str_replace(self::OLD_DISPLAY, self::NEW_DISPLAY, $style->title);
|
||||
// Also catch lowercase variant
|
||||
$newTitle = str_replace(self::OLD_NAME, self::NEW_NAME, $newTitle);
|
||||
|
||||
$newParams = $style->params;
|
||||
if (is_string($newParams)) {
|
||||
$newParams = str_replace(self::OLD_NAME, self::NEW_NAME, $newParams);
|
||||
}
|
||||
|
||||
$update = $db->getQuery(true)
|
||||
->update('#__template_styles')
|
||||
->set($db->quoteName('template') . ' = ' . $db->quote(self::NEW_NAME))
|
||||
->set($db->quoteName('title') . ' = ' . $db->quote($newTitle))
|
||||
->set($db->quoteName('params') . ' = ' . $db->quote($newParams))
|
||||
->where('id = ' . (int) $style->id);
|
||||
|
||||
try {
|
||||
$db->setQuery($update)->execute();
|
||||
} catch (\Throwable $e) {
|
||||
self::log('Bridge: style update failed for id=' . $style->id . ': ' . $e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($styles)) {
|
||||
self::log('Bridge: updated ' . count($styles) . ' template style(s) in #__template_styles');
|
||||
}
|
||||
|
||||
// 3. Update #__menu — fix template_style_id link field references
|
||||
// Menu items store the template name in the link for template-specific assignments
|
||||
try {
|
||||
$query = $db->getQuery(true)
|
||||
->update('#__menu')
|
||||
->set($db->quoteName('link') . ' = REPLACE(' . $db->quoteName('link') . ', '
|
||||
. $db->quote(self::OLD_NAME) . ', ' . $db->quote(self::NEW_NAME) . ')')
|
||||
->where($db->quoteName('link') . ' LIKE ' . $db->quote('%' . self::OLD_NAME . '%'));
|
||||
$db->setQuery($query)->execute();
|
||||
$affected = $db->getAffectedRows();
|
||||
if ($affected > 0) {
|
||||
self::log("Bridge: updated {$affected} menu link(s)");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
self::log('Bridge: #__menu update failed: ' . $e->getMessage(), 'warning');
|
||||
}
|
||||
|
||||
// 4. Update #__update_sites — point to MokoOnyx updates.xml
|
||||
try {
|
||||
$newLocation = 'https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/updates.xml';
|
||||
$query = $db->getQuery(true)
|
||||
->update('#__update_sites')
|
||||
->set($db->quoteName('location') . ' = ' . $db->quote($newLocation))
|
||||
->set($db->quoteName('name') . ' = ' . $db->quote(self::NEW_DISPLAY))
|
||||
->where($db->quoteName('location') . ' LIKE ' . $db->quote('%MokoCassiopeia%'));
|
||||
$db->setQuery($query)->execute();
|
||||
$affected = $db->getAffectedRows();
|
||||
if ($affected > 0) {
|
||||
self::log("Bridge: updated {$affected} update site(s) to MokoOnyx");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
self::log('Bridge: #__update_sites update failed: ' . $e->getMessage(), 'warning');
|
||||
}
|
||||
|
||||
// 5. Update #__updates — clear cached updates for old extension
|
||||
try {
|
||||
$query = $db->getQuery(true)
|
||||
->delete('#__updates')
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote(self::OLD_NAME));
|
||||
$db->setQuery($query)->execute();
|
||||
$affected = $db->getAffectedRows();
|
||||
if ($affected > 0) {
|
||||
self::log("Bridge: cleared {$affected} cached update(s) for old extension");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
self::log('Bridge: #__updates cleanup failed: ' . $e->getMessage(), 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
private static function notifyUser($app): void
|
||||
{
|
||||
$app->enqueueMessage(
|
||||
'<strong>MokoCassiopeia has been renamed to MokoOnyx.</strong><br>'
|
||||
. 'Your template files, settings, and menu assignments have been migrated automatically. '
|
||||
. 'MokoOnyx is now your active site template.',
|
||||
'success'
|
||||
);
|
||||
}
|
||||
|
||||
private static function log(string $message, string $priority = 'info'): void
|
||||
{
|
||||
$priorities = [
|
||||
'info' => Log::INFO,
|
||||
'warning' => Log::WARNING,
|
||||
'error' => Log::ERROR,
|
||||
];
|
||||
|
||||
Log::addLogger(
|
||||
['text_file' => 'mokocassiopeia_bridge.log.php'],
|
||||
Log::ALL,
|
||||
['mokocassiopeia_bridge']
|
||||
);
|
||||
|
||||
Log::add($message, $priorities[$priority] ?? Log::INFO, 'mokocassiopeia_bridge');
|
||||
}
|
||||
}
|
||||
385
src/script.php
385
src/script.php
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
@@ -8,12 +8,11 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Template install/update/uninstall script.
|
||||
* Joomla calls the methods in this class automatically during template
|
||||
* install, update, and uninstall via the <scriptfile> element in
|
||||
* templateDetails.xml.
|
||||
* Joomla 5 and 6 compatible — uses the InstallerScriptInterface when
|
||||
* available, falls back to the legacy class-based approach otherwise.
|
||||
* MokoCassiopeia install/update/uninstall script.
|
||||
*
|
||||
* On update: renames the template to MokoOnyx (dirs + database),
|
||||
* copies all style params, creates matching styles, copies user files,
|
||||
* and redirects the update server. No external downloads needed.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
@@ -24,33 +23,23 @@ use Joomla\CMS\Log\Log;
|
||||
|
||||
class Tpl_MokocassiopeiaInstallerScript
|
||||
{
|
||||
/**
|
||||
* Minimum PHP version required by this template.
|
||||
*/
|
||||
private const MIN_PHP = '8.1.0';
|
||||
|
||||
/**
|
||||
* Minimum Joomla version required by this template.
|
||||
*/
|
||||
private const MIN_PHP = '8.1.0';
|
||||
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';
|
||||
|
||||
private const ONYX_UPDATES_URL = 'https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/updates.xml';
|
||||
|
||||
// ── Joomla lifecycle methods ───────────────────────────────────────
|
||||
|
||||
public function preflight(string $type, InstallerAdapter $parent): bool
|
||||
{
|
||||
if (version_compare(PHP_VERSION, self::MIN_PHP, '<')) {
|
||||
Factory::getApplication()->enqueueMessage(
|
||||
sprintf(
|
||||
'MokoCassiopeia requires PHP %s or later. You are running PHP %s.',
|
||||
self::MIN_PHP,
|
||||
PHP_VERSION
|
||||
),
|
||||
sprintf('MokoCassiopeia requires PHP %s+. Running %s.', self::MIN_PHP, PHP_VERSION),
|
||||
'error'
|
||||
);
|
||||
return false;
|
||||
@@ -58,11 +47,7 @@ class Tpl_MokocassiopeiaInstallerScript
|
||||
|
||||
if (version_compare(JVERSION, self::MIN_JOOMLA, '<')) {
|
||||
Factory::getApplication()->enqueueMessage(
|
||||
sprintf(
|
||||
'MokoCassiopeia requires Joomla %s or later. You are running Joomla %s.',
|
||||
self::MIN_JOOMLA,
|
||||
JVERSION
|
||||
),
|
||||
sprintf('MokoCassiopeia requires Joomla %s+. Running %s.', self::MIN_JOOMLA, JVERSION),
|
||||
'error'
|
||||
);
|
||||
return false;
|
||||
@@ -71,167 +56,231 @@ class Tpl_MokocassiopeiaInstallerScript
|
||||
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('MokoCassiopeia template installed.');
|
||||
$this->log('MokoCassiopeia 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('MokoCassiopeia update() called — version ' . ($parent->getManifest()->version ?? 'unknown'));
|
||||
|
||||
// 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(
|
||||
'MokoCassiopeia: %d new CSS variable(s) were added to your custom palette files. '
|
||||
. 'Review them in your light.custom.css and/or dark.custom.css to customise the new defaults.',
|
||||
$synced
|
||||
),
|
||||
'notice'
|
||||
);
|
||||
}
|
||||
|
||||
// Bridge migration runs in postflight() — not here — to avoid double execution
|
||||
$this->log('MokoCassiopeia update() — version ' . ($parent->getManifest()->version ?? '?'));
|
||||
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('MokoCassiopeia template uninstalled.');
|
||||
$this->log('MokoCassiopeia 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
|
||||
{
|
||||
// Bridge migration runs in postflight (more reliable than update() for templates)
|
||||
if ($type === 'update') {
|
||||
$bridgeScript = $parent->getParent()->getPath('source') . '/helper/bridge.php';
|
||||
if (!is_file($bridgeScript)) {
|
||||
$bridgeScript = __DIR__ . '/helper/bridge.php';
|
||||
}
|
||||
if (is_file($bridgeScript)) {
|
||||
require_once $bridgeScript;
|
||||
if (class_exists('MokoBridgeMigration')) {
|
||||
$this->logMessage('Running MokoOnyx bridge migration from postflight...');
|
||||
MokoBridgeMigration::run();
|
||||
}
|
||||
}
|
||||
$this->log('=== MokoCassiopeia → MokoOnyx bridge ===');
|
||||
$this->bridge();
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
private function syncCustomVariables(InstallerAdapter $parent): int
|
||||
// ── Bridge: rename-in-place + DB migration ─────────────────────────
|
||||
|
||||
private function bridge(): void
|
||||
{
|
||||
$templateDir = $parent->getParent()->getPath('source');
|
||||
$app = Factory::getApplication();
|
||||
|
||||
// The sync script lives alongside this script in the template root.
|
||||
$syncScript = $templateDir . '/sync_custom_vars.php';
|
||||
|
||||
if (!is_file($syncScript)) {
|
||||
$this->logMessage('CSS variable sync script not found at: ' . $syncScript, 'warning');
|
||||
return 0;
|
||||
}
|
||||
|
||||
require_once $syncScript;
|
||||
|
||||
if (!class_exists('MokoCssVarSync')) {
|
||||
$this->logMessage('MokoCssVarSync class not found after loading script.', 'warning');
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
$joomlaRoot = JPATH_ROOT;
|
||||
$results = MokoCssVarSync::run($joomlaRoot);
|
||||
|
||||
$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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $totalAdded;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logMessage('CSS variable sync failed: ' . $e->getMessage(), 'error');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = [
|
||||
'info' => Log::INFO,
|
||||
'warning' => Log::WARNING,
|
||||
'error' => Log::ERROR,
|
||||
];
|
||||
|
||||
Log::addLogger(
|
||||
['text_file' => 'mokocassiopeia.log.php'],
|
||||
Log::ALL,
|
||||
['mokocassiopeia']
|
||||
// 1. Rename template directory
|
||||
$templateRenamed = $this->renameDir(
|
||||
JPATH_ROOT . '/templates/' . self::OLD_NAME,
|
||||
JPATH_ROOT . '/templates/' . self::NEW_NAME,
|
||||
'template'
|
||||
);
|
||||
|
||||
Log::add($message, $priorities[$priority] ?? Log::INFO, 'mokocassiopeia');
|
||||
if (!$templateRenamed && !is_dir(JPATH_ROOT . '/templates/' . self::NEW_NAME)) {
|
||||
$app->enqueueMessage(
|
||||
'Could not rename template directory to MokoOnyx. '
|
||||
. 'Please rename <code>templates/mokocassiopeia</code> to <code>templates/mokoonyx</code> manually.',
|
||||
'warning'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Rename media directory
|
||||
$this->renameDir(
|
||||
JPATH_ROOT . '/media/templates/site/' . self::OLD_NAME,
|
||||
JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME,
|
||||
'media'
|
||||
);
|
||||
|
||||
// 3. Update #__extensions
|
||||
$this->updateExtensions();
|
||||
|
||||
// 4. Migrate template styles (create matching MokoOnyx styles with same params)
|
||||
$this->migrateStyles();
|
||||
|
||||
// 5. Copy user files (custom themes, user.css, user.js)
|
||||
$this->copyUserFiles();
|
||||
|
||||
// 6. Redirect update server to MokoOnyx
|
||||
$this->updateUpdateServer();
|
||||
|
||||
// 7. Notify
|
||||
$app->enqueueMessage(
|
||||
'<strong>MokoCassiopeia has been renamed to MokoOnyx.</strong><br>'
|
||||
. 'All template settings, styles, and custom files have been migrated. '
|
||||
. 'MokoOnyx is now your active site template.',
|
||||
'success'
|
||||
);
|
||||
|
||||
$this->log('=== Bridge completed ===');
|
||||
}
|
||||
|
||||
// ── Bridge helpers ─────────────────────────────────────────────────
|
||||
|
||||
private function renameDir(string $old, string $new, string $label): bool
|
||||
{
|
||||
if (!is_dir($old)) {
|
||||
$this->log("Bridge: {$label} dir not found ({$old}) — skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_dir($new)) {
|
||||
$this->log("Bridge: {$label} dir already exists ({$new}) — skipping rename.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (@rename($old, $new)) {
|
||||
$this->log("Bridge: renamed {$label} dir → " . self::NEW_NAME);
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->log("Bridge: failed to rename {$label} dir.", 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
private function updateExtensions(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
try {
|
||||
$query = $db->getQuery(true)
|
||||
->update('#__extensions')
|
||||
->set($db->quoteName('element') . ' = ' . $db->quote(self::NEW_NAME))
|
||||
->set($db->quoteName('name') . ' = ' . $db->quote(self::NEW_NAME))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote(self::OLD_NAME))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('template'));
|
||||
$db->setQuery($query)->execute();
|
||||
|
||||
$n = $db->getAffectedRows();
|
||||
if ($n > 0) {
|
||||
$this->log("Bridge: updated {$n} row(s) in #__extensions.");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->log('Bridge: #__extensions failed: ' . $e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
private function migrateStyles(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Get all MokoCassiopeia styles (may already be renamed to mokoonyx by updateExtensions)
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from('#__template_styles')
|
||||
->where('(' . $db->quoteName('template') . ' = ' . $db->quote(self::OLD_NAME)
|
||||
. ' OR ' . $db->quoteName('template') . ' = ' . $db->quote(self::NEW_NAME) . ')')
|
||||
->where($db->quoteName('client_id') . ' = 0');
|
||||
$styles = $db->setQuery($query)->loadObjectList();
|
||||
|
||||
if (empty($styles)) {
|
||||
$this->log('Bridge: no styles found to migrate.');
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($styles as $style) {
|
||||
$newTitle = str_replace(self::OLD_DISPLAY, self::NEW_DISPLAY, $style->title);
|
||||
$newTitle = str_replace(self::OLD_NAME, self::NEW_NAME, $newTitle);
|
||||
$newParams = is_string($style->params)
|
||||
? str_replace(self::OLD_NAME, self::NEW_NAME, $style->params)
|
||||
: $style->params;
|
||||
|
||||
$update = $db->getQuery(true)
|
||||
->update('#__template_styles')
|
||||
->set($db->quoteName('template') . ' = ' . $db->quote(self::NEW_NAME))
|
||||
->set($db->quoteName('title') . ' = ' . $db->quote($newTitle))
|
||||
->set($db->quoteName('params') . ' = ' . $db->quote($newParams))
|
||||
->where('id = ' . (int) $style->id);
|
||||
|
||||
try {
|
||||
$db->setQuery($update)->execute();
|
||||
} catch (\Throwable $e) {
|
||||
$this->log('Bridge: style update failed (id=' . $style->id . '): ' . $e->getMessage(), 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
$this->log('Bridge: migrated ' . count($styles) . ' style(s).');
|
||||
}
|
||||
|
||||
private function copyUserFiles(): void
|
||||
{
|
||||
$media = JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME;
|
||||
if (!is_dir($media)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// User files are already in the renamed media dir — nothing to copy.
|
||||
// They were moved with the rename. Log for clarity.
|
||||
$this->log('Bridge: user files preserved via directory rename.');
|
||||
}
|
||||
|
||||
private function updateUpdateServer(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
try {
|
||||
$query = $db->getQuery(true)
|
||||
->update('#__update_sites')
|
||||
->set($db->quoteName('location') . ' = ' . $db->quote(self::ONYX_UPDATES_URL))
|
||||
->set($db->quoteName('name') . ' = ' . $db->quote(self::NEW_DISPLAY))
|
||||
->where($db->quoteName('location') . ' LIKE ' . $db->quote('%MokoCassiopeia%'));
|
||||
$db->setQuery($query)->execute();
|
||||
|
||||
$n = $db->getAffectedRows();
|
||||
if ($n > 0) {
|
||||
$this->log("Bridge: redirected {$n} update site(s) to MokoOnyx.");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->log('Bridge: update server redirect failed: ' . $e->getMessage(), 'warning');
|
||||
}
|
||||
|
||||
// Clear cached updates for old element
|
||||
try {
|
||||
$query = $db->getQuery(true)
|
||||
->delete('#__updates')
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote(self::OLD_NAME));
|
||||
$db->setQuery($query)->execute();
|
||||
} catch (\Throwable $e) {
|
||||
// Not critical
|
||||
}
|
||||
}
|
||||
|
||||
// ── Logging ────────────────────────────────────────────────────────
|
||||
|
||||
private function log(string $message, string $priority = 'info'): void
|
||||
{
|
||||
static $init = false;
|
||||
if (!$init) {
|
||||
Log::addLogger(
|
||||
['text_file' => 'mokocassiopeia_bridge.log.php'],
|
||||
Log::ALL,
|
||||
['mokocassiopeia_bridge']
|
||||
);
|
||||
$init = true;
|
||||
}
|
||||
|
||||
$levels = ['info' => Log::INFO, 'warning' => Log::WARNING, 'error' => Log::ERROR];
|
||||
Log::add($message, $levels[$priority] ?? Log::INFO, 'mokocassiopeia_bridge');
|
||||
}
|
||||
}
|
||||
|
||||
17
updates.xml
17
updates.xml
@@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
VERSION: 03.10.14
|
||||
VERSION: 03.10.17
|
||||
-->
|
||||
|
||||
<updates>
|
||||
@@ -17,9 +17,8 @@
|
||||
<creationDate>2026-04-21</creationDate>
|
||||
<infourl title='MokoCassiopeia Dev'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/development</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/mokoonyx-01.00.01-dev.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/development/mokocassiopeia-03.10.17-dev.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>089563017322317f989c49f8260d6f84cf2b84235cad4584504b716b9c429e83</sha256>
|
||||
<tags><tag>development</tag></tags>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
@@ -38,9 +37,8 @@
|
||||
<creationDate>2026-04-19</creationDate>
|
||||
<infourl title='MokoCassiopeia Alpha'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/alpha</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/alpha/mokoonyx-01.00.00.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.10.13.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>954c26f29af533c58658ed312b4b6261cc9e783dcf0cd9d879d34df6e8a421f4</sha256>
|
||||
<tags><tag>alpha</tag></tags>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
@@ -59,9 +57,8 @@
|
||||
<creationDate>2026-04-19</creationDate>
|
||||
<infourl title='MokoCassiopeia Beta'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/beta</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/beta/mokoonyx-01.00.00.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.10.13.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>954c26f29af533c58658ed312b4b6261cc9e783dcf0cd9d879d34df6e8a421f4</sha256>
|
||||
<tags><tag>beta</tag></tags>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
@@ -80,9 +77,8 @@
|
||||
<creationDate>2026-04-19</creationDate>
|
||||
<infourl title='MokoCassiopeia RC'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/release-candidate</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/release-candidate/mokoonyx-01.00.00.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.10.13.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>954c26f29af533c58658ed312b4b6261cc9e783dcf0cd9d879d34df6e8a421f4</sha256>
|
||||
<tags><tag>rc</tag></tags>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
@@ -101,9 +97,8 @@
|
||||
<creationDate>2026-04-19</creationDate>
|
||||
<infourl title='MokoCassiopeia'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/v01/mokoonyx-01.00.00.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.10.13.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>954c26f29af533c58658ed312b4b6261cc9e783dcf0cd9d879d34df6e8a421f4</sha256>
|
||||
<tags><tag>stable</tag></tags>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
|
||||
Reference in New Issue
Block a user