Bridge: rewrite as rename-in-place + DB update (no external downloads)
- Rename templates/mokocassiopeia → mokoonyx - Rename media/templates/site/mokocassiopeia → mokoonyx - Update #__extensions element and name - Update #__template_styles template, title, and params - Update #__menu link references - Update #__update_sites to point to MokoOnyx updates.xml - Clear #__updates cached entries for old extension - No HTTP requests, no ZIP downloads, no Installer conflicts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,14 +10,13 @@
|
|||||||
/**
|
/**
|
||||||
* Bridge migration helper — MokoCassiopeia → MokoOnyx
|
* Bridge migration helper — MokoCassiopeia → MokoOnyx
|
||||||
*
|
*
|
||||||
* Downloads and installs MokoOnyx from the Gitea release, then migrates
|
* Renames template files/folders and updates the database to migrate
|
||||||
* template styles and menu assignments from MokoCassiopeia.
|
* from MokoCassiopeia to MokoOnyx. No external downloads required.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
defined('_JEXEC') or die;
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
use Joomla\CMS\Factory;
|
||||||
use Joomla\CMS\Installer\Installer;
|
|
||||||
use Joomla\CMS\Log\Log;
|
use Joomla\CMS\Log\Log;
|
||||||
|
|
||||||
class MokoBridgeMigration
|
class MokoBridgeMigration
|
||||||
@@ -28,12 +27,6 @@ class MokoBridgeMigration
|
|||||||
private const OLD_DISPLAY = 'MokoCassiopeia';
|
private const OLD_DISPLAY = 'MokoCassiopeia';
|
||||||
private const NEW_DISPLAY = 'MokoOnyx';
|
private const NEW_DISPLAY = 'MokoOnyx';
|
||||||
|
|
||||||
/** Raw URL for MokoOnyx updates.xml on main — used to discover the stable download URL */
|
|
||||||
private const UPDATES_XML_URL = 'https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/updates.xml';
|
|
||||||
|
|
||||||
/** Fallback URL if updates.xml cannot be parsed */
|
|
||||||
private const FALLBACK_URL = 'https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/v01/mokoonyx-01.00.00.zip';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the full migration.
|
* Run the full migration.
|
||||||
*/
|
*/
|
||||||
@@ -41,45 +34,32 @@ class MokoBridgeMigration
|
|||||||
{
|
{
|
||||||
$app = Factory::getApplication();
|
$app = Factory::getApplication();
|
||||||
|
|
||||||
// Check if MokoOnyx is already installed
|
// Already migrated?
|
||||||
if (is_dir(JPATH_ROOT . '/templates/' . self::NEW_NAME)) {
|
if (is_dir(JPATH_ROOT . '/templates/' . self::NEW_NAME)) {
|
||||||
self::log('MokoOnyx already installed — skipping download.');
|
self::log('MokoOnyx template dir already exists — updating database only.');
|
||||||
self::migrateStyles();
|
self::updateDatabase();
|
||||||
self::notifyUser($app);
|
self::notifyUser($app);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Try downloading and installing MokoOnyx from Gitea release
|
// 1. Rename template directory
|
||||||
$installed = false;
|
$renamed = self::renameTemplateDir();
|
||||||
$zipPath = self::downloadRelease();
|
if (!$renamed) {
|
||||||
if ($zipPath) {
|
|
||||||
$installed = self::installPackage($zipPath);
|
|
||||||
@unlink($zipPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Fallback: copy from MokoCassiopeia and rename
|
|
||||||
if (!$installed) {
|
|
||||||
self::log('Bridge: download/install failed, falling back to file copy');
|
|
||||||
$installed = self::copyAndRename();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$installed) {
|
|
||||||
$app->enqueueMessage(
|
$app->enqueueMessage(
|
||||||
'MokoOnyx migration: automatic installation failed. '
|
'MokoOnyx migration: could not rename template directory. '
|
||||||
. 'Please install MokoOnyx manually from '
|
. 'Please rename templates/mokocassiopeia to templates/mokoonyx manually.',
|
||||||
. '<a href="https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases" target="_blank">Gitea Releases</a>.',
|
|
||||||
'warning'
|
'warning'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Copy user files (custom themes, user.css, user.js)
|
// 2. Rename media directory
|
||||||
self::copyAndRename();
|
self::renameMediaDir();
|
||||||
|
|
||||||
// 4. Migrate template styles and params
|
// 3. Update database (extensions, template_styles, menu assignments)
|
||||||
self::migrateStyles();
|
self::updateDatabase();
|
||||||
|
|
||||||
// 5. Notify admin
|
// 4. Notify admin
|
||||||
self::notifyUser($app);
|
self::notifyUser($app);
|
||||||
|
|
||||||
self::log('Bridge migration completed successfully.');
|
self::log('Bridge migration completed successfully.');
|
||||||
@@ -87,308 +67,176 @@ class MokoBridgeMigration
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download the MokoOnyx ZIP to Joomla's tmp directory.
|
* Rename templates/mokocassiopeia → templates/mokoonyx
|
||||||
*
|
|
||||||
* Reads MokoOnyx's updates.xml on main to discover the current stable
|
|
||||||
* download URL, falling back to a hardcoded URL if parsing fails.
|
|
||||||
*/
|
*/
|
||||||
private static function downloadRelease(): ?string
|
private static function renameTemplateDir(): bool
|
||||||
{
|
{
|
||||||
$tmpDir = Factory::getApplication()->get('tmp_path', JPATH_ROOT . '/tmp');
|
$oldDir = JPATH_ROOT . '/templates/' . self::OLD_NAME;
|
||||||
$zipPath = $tmpDir . '/mokoonyx-install.zip';
|
$newDir = JPATH_ROOT . '/templates/' . self::NEW_NAME;
|
||||||
|
|
||||||
// 1. Discover the stable download URL from MokoOnyx's updates.xml
|
if (!is_dir($oldDir)) {
|
||||||
$releaseUrl = self::discoverStableUrl();
|
self::log('Bridge: old template dir not found: ' . $oldDir, 'warning');
|
||||||
if (!$releaseUrl) {
|
|
||||||
self::log('Bridge: could not discover release URL from updates.xml, using fallback');
|
|
||||||
$releaseUrl = self::FALLBACK_URL;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::log('Bridge: downloading MokoOnyx from ' . $releaseUrl);
|
|
||||||
|
|
||||||
// 2. Download the ZIP
|
|
||||||
$content = self::httpGet($releaseUrl);
|
|
||||||
|
|
||||||
if ($content === false || strlen($content) < 1000) {
|
|
||||||
self::log('Bridge: failed to download MokoOnyx ZIP from ' . $releaseUrl, 'error');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file_put_contents($zipPath, $content) === false) {
|
|
||||||
self::log('Bridge: failed to write ZIP to ' . $zipPath, 'error');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::log('Bridge: downloaded MokoOnyx ZIP (' . strlen($content) . ' bytes)');
|
|
||||||
return $zipPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch MokoOnyx's updates.xml and extract the stable channel ZIP URL.
|
|
||||||
*
|
|
||||||
* Always targets the stable channel — the bridge should only install
|
|
||||||
* production-ready builds of MokoOnyx.
|
|
||||||
*/
|
|
||||||
private static function discoverStableUrl(): ?string
|
|
||||||
{
|
|
||||||
$xml = self::httpGet(self::UPDATES_XML_URL);
|
|
||||||
if ($xml === false || strlen($xml) < 100) {
|
|
||||||
self::log('Bridge: failed to fetch MokoOnyx updates.xml', 'warning');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
libxml_use_internal_errors(true);
|
|
||||||
$doc = simplexml_load_string($xml);
|
|
||||||
libxml_clear_errors();
|
|
||||||
|
|
||||||
if (!$doc) {
|
|
||||||
self::log('Bridge: failed to parse MokoOnyx updates.xml', 'warning');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the stable <update> block
|
|
||||||
foreach ($doc->update as $update) {
|
|
||||||
$tags = $update->tags->tag ?? [];
|
|
||||||
foreach ($tags as $tag) {
|
|
||||||
if ((string) $tag === 'stable') {
|
|
||||||
foreach ($update->downloads->downloadurl as $dl) {
|
|
||||||
$format = (string) ($dl['format'] ?? '');
|
|
||||||
$url = trim((string) $dl);
|
|
||||||
if ($format === 'zip' && !empty($url)) {
|
|
||||||
self::log('Bridge: discovered stable URL: ' . $url);
|
|
||||||
return $url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self::log('Bridge: no stable ZIP URL found in MokoOnyx updates.xml', 'warning');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP GET helper — tries file_get_contents then cURL.
|
|
||||||
*
|
|
||||||
* @return string|false Response body or false on failure.
|
|
||||||
*/
|
|
||||||
private static function httpGet(string $url)
|
|
||||||
{
|
|
||||||
$content = false;
|
|
||||||
|
|
||||||
if (ini_get('allow_url_fopen')) {
|
|
||||||
$ctx = stream_context_create([
|
|
||||||
'http' => [
|
|
||||||
'timeout' => 60,
|
|
||||||
'follow_location' => true,
|
|
||||||
'max_redirects' => 5,
|
|
||||||
],
|
|
||||||
'ssl' => [
|
|
||||||
'verify_peer' => true,
|
|
||||||
'verify_peer_name' => true,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$content = @file_get_contents($url, false, $ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($content === false && function_exists('curl_init')) {
|
|
||||||
$ch = curl_init($url);
|
|
||||||
curl_setopt_array($ch, [
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
CURLOPT_FOLLOWLOCATION => true,
|
|
||||||
CURLOPT_MAXREDIRS => 5,
|
|
||||||
CURLOPT_TIMEOUT => 60,
|
|
||||||
CURLOPT_SSL_VERIFYPEER => true,
|
|
||||||
]);
|
|
||||||
$content = curl_exec($ch);
|
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
if ($httpCode !== 200) {
|
|
||||||
$content = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $content;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Install the downloaded ZIP via Joomla's Installer.
|
|
||||||
*/
|
|
||||||
private static function installPackage(string $zipPath): bool
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$installer = Installer::getInstance();
|
|
||||||
|
|
||||||
$tmpDir = Factory::getApplication()->get('tmp_path', JPATH_ROOT . '/tmp');
|
|
||||||
$extractDir = $tmpDir . '/mokoonyx_install_' . time();
|
|
||||||
|
|
||||||
$zip = new \ZipArchive();
|
|
||||||
if ($zip->open($zipPath) !== true) {
|
|
||||||
self::log('Bridge: failed to open ZIP', 'error');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$zip->extractTo($extractDir);
|
|
||||||
$zip->close();
|
|
||||||
|
|
||||||
$result = $installer->install($extractDir);
|
|
||||||
|
|
||||||
if (is_dir($extractDir)) {
|
|
||||||
self::removeDirectory($extractDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($result) {
|
|
||||||
self::log('Bridge: MokoOnyx installed via Joomla Installer');
|
|
||||||
} else {
|
|
||||||
self::log('Bridge: Joomla Installer returned false', 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (bool) $result;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
self::log('Bridge: install failed: ' . $e->getMessage(), 'error');
|
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migrate template styles and menu assignments from MokoCassiopeia to MokoOnyx.
|
* Rename media/templates/site/mokocassiopeia → mokoonyx
|
||||||
*/
|
*/
|
||||||
private static function migrateStyles(): void
|
private static function renameMediaDir(): void
|
||||||
{
|
|
||||||
$db = Factory::getDbo();
|
|
||||||
|
|
||||||
$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)) {
|
|
||||||
self::log('No MokoCassiopeia styles found — nothing to migrate.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($oldStyles as $oldStyle) {
|
|
||||||
$newTitle = str_replace(self::OLD_DISPLAY, self::NEW_DISPLAY, $oldStyle->title);
|
|
||||||
$query = $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($query)->loadResult() > 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$newStyle = clone $oldStyle;
|
|
||||||
unset($newStyle->id);
|
|
||||||
$newStyle->template = self::NEW_NAME;
|
|
||||||
$newStyle->title = $newTitle;
|
|
||||||
|
|
||||||
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 ($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();
|
|
||||||
|
|
||||||
self::log('Set MokoOnyx as default site template.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self::log('Migrated ' . count($oldStyles) . ' template style(s).');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy user-specific files from MokoCassiopeia to MokoOnyx.
|
|
||||||
* Only copies custom themes, user.css, and user.js — not the full template.
|
|
||||||
* MokoOnyx must already be installed (via download or manual).
|
|
||||||
*/
|
|
||||||
private static function copyAndRename(): bool
|
|
||||||
{
|
{
|
||||||
$oldMedia = JPATH_ROOT . '/media/templates/site/' . self::OLD_NAME;
|
$oldMedia = JPATH_ROOT . '/media/templates/site/' . self::OLD_NAME;
|
||||||
$newMedia = JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME;
|
$newMedia = JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME;
|
||||||
|
|
||||||
if (!is_dir($newMedia)) {
|
if (!is_dir($oldMedia)) {
|
||||||
self::log('Bridge: MokoOnyx media dir not found — cannot copy user files', 'warning');
|
self::log('Bridge: old media dir not found — skipping');
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$copied = 0;
|
if (is_dir($newMedia)) {
|
||||||
|
self::log('Bridge: new media dir already exists — skipping rename');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Copy custom theme palettes
|
if (@rename($oldMedia, $newMedia)) {
|
||||||
$userFiles = [
|
self::log('Bridge: renamed media dir to ' . self::NEW_NAME);
|
||||||
'css/theme/light.custom.css',
|
} else {
|
||||||
'css/theme/dark.custom.css',
|
self::log('Bridge: failed to rename media dir', 'warning');
|
||||||
'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',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($userFiles as $relPath) {
|
/**
|
||||||
$srcFile = $oldMedia . '/' . $relPath;
|
* Update all database references from mokocassiopeia → mokoonyx.
|
||||||
$dstFile = $newMedia . '/' . $relPath;
|
*/
|
||||||
if (is_file($srcFile) && !is_file($dstFile)) {
|
private static function updateDatabase(): void
|
||||||
$dstDir = dirname($dstFile);
|
{
|
||||||
if (!is_dir($dstDir)) {
|
$db = Factory::getDbo();
|
||||||
mkdir($dstDir, 0755, true);
|
|
||||||
}
|
// 1. Update #__extensions — change element and name
|
||||||
copy($srcFile, $dstFile);
|
$query = $db->getQuery(true)
|
||||||
$copied++;
|
->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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy favicon directory
|
if (!empty($styles)) {
|
||||||
$faviconSrc = JPATH_ROOT . '/images/favicons';
|
self::log('Bridge: updated ' . count($styles) . ' template style(s) in #__template_styles');
|
||||||
if (is_dir($faviconSrc)) {
|
|
||||||
self::log('Bridge: favicons already at images/favicons — shared between templates');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self::log("Bridge: copied {$copied} user file(s) to MokoOnyx");
|
// 3. Update #__menu — fix template_style_id link field references
|
||||||
return true;
|
// 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
|
private static function notifyUser($app): void
|
||||||
{
|
{
|
||||||
$app->enqueueMessage(
|
$app->enqueueMessage(
|
||||||
'<strong>MokoCassiopeia has been renamed to MokoOnyx.</strong><br>'
|
'<strong>MokoCassiopeia has been renamed to MokoOnyx.</strong><br>'
|
||||||
. 'Your template settings have been migrated automatically. '
|
. 'Your template files, settings, and menu assignments have been migrated automatically. '
|
||||||
. 'MokoOnyx is now your active site template. '
|
. 'MokoOnyx is now your active site template.',
|
||||||
. 'You can safely uninstall MokoCassiopeia from Extensions → Manage.',
|
|
||||||
'success'
|
'success'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function removeDirectory(string $dir): void
|
|
||||||
{
|
|
||||||
$items = new \RecursiveIteratorIterator(
|
|
||||||
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS),
|
|
||||||
\RecursiveIteratorIterator::CHILD_FIRST
|
|
||||||
);
|
|
||||||
foreach ($items as $item) {
|
|
||||||
$item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
|
|
||||||
}
|
|
||||||
rmdir($dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function log(string $message, string $priority = 'info'): void
|
private static function log(string $message, string $priority = 'info'): void
|
||||||
{
|
{
|
||||||
$priorities = [
|
$priorities = [
|
||||||
|
|||||||
Reference in New Issue
Block a user