diff --git a/src/helper/bridge.php b/src/helper/bridge.php index 43a168a..9172027 100644 --- a/src/helper/bridge.php +++ b/src/helper/bridge.php @@ -28,8 +28,11 @@ class MokoBridgeMigration private const OLD_DISPLAY = 'MokoCassiopeia'; private const NEW_DISPLAY = 'MokoOnyx'; - /** URL to the latest MokoOnyx stable release ZIP */ - private const RELEASE_URL = 'https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/v01/mokoonyx-01.00.00.zip'; + /** 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. @@ -85,15 +88,94 @@ class MokoBridgeMigration /** * Download the MokoOnyx ZIP to Joomla's tmp directory. + * + * 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 { $tmpDir = Factory::getApplication()->get('tmp_path', JPATH_ROOT . '/tmp'); $zipPath = $tmpDir . '/mokoonyx-install.zip'; + // 1. Discover the stable download URL from MokoOnyx's updates.xml + $releaseUrl = self::discoverStableUrl(); + 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 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; - // Method 1: file_get_contents if (ini_get('allow_url_fopen')) { $ctx = stream_context_create([ 'http' => [ @@ -106,12 +188,11 @@ class MokoBridgeMigration 'verify_peer_name' => true, ], ]); - $content = @file_get_contents(self::RELEASE_URL, false, $ctx); + $content = @file_get_contents($url, false, $ctx); } - // Method 2: cURL if ($content === false && function_exists('curl_init')) { - $ch = curl_init(self::RELEASE_URL); + $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, @@ -128,18 +209,7 @@ class MokoBridgeMigration } } - if ($content === false || strlen($content) < 1000) { - self::log('Bridge: failed to download MokoOnyx ZIP from ' . self::RELEASE_URL, '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; + return $content; } /** diff --git a/src/script.php b/src/script.php index deec459..92e2333 100644 --- a/src/script.php +++ b/src/script.php @@ -113,15 +113,7 @@ class Tpl_MokocassiopeiaInstallerScript ); } - // Bridge migration: MokoCassiopeia → MokoOnyx - $bridgeScript = __DIR__ . '/helper/bridge.php'; - if (is_file($bridgeScript)) { - require_once $bridgeScript; - if (class_exists('MokoBridgeMigration')) { - MokoBridgeMigration::run(); - } - } - + // Bridge migration runs in postflight() — not here — to avoid double execution return true; }