From d792b7ff0c72aef72525834965f3e4bc896f1f52 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 6 Jun 2026 07:02:22 -0500 Subject: [PATCH] fix: preserve download keys across Joomla extension updates (#187) Joomla's installer can wipe extra_query (dlid) from #__update_sites when rebuilding or reinstalling. The core plugin now backs up all download keys and auto-restores any that get cleared. Runs on every admin page load with a single lightweight query. Closes #187 Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Extension/MokoWaaS.php | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php b/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php index 5900049a..2cd4e5a3 100644 --- a/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php +++ b/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php @@ -166,6 +166,12 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface { $this->handleMokoApi($mokoAction); } + + // Preserve download keys (admin only, lightweight check) + if ($this->app->isClient('administrator')) + { + $this->preserveDownloadKeys(); + } } /** @@ -2046,6 +2052,107 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface return $this->masterNames; } + // ------------------------------------------------------------------ + // Download Key Preservation + // ------------------------------------------------------------------ + /** + * Preserve download keys across Joomla extension updates. + * + * Joomla's installer can wipe the extra_query column (which holds + * download keys / dlid) when rebuilding or reinstalling update sites. + * This method keeps a backup of all non-empty extra_query values and + * restores any that get cleared. + * + * @return void + * + * @since 02.34.12 + */ + protected function preserveDownloadKeys(): void + { + try + { + $db = Factory::getDbo(); + // Load current extra_query values for all update sites + $query = $db->getQuery(true) + ->select([ + $db->quoteName('update_site_id'), + $db->quoteName('extra_query'), + $db->quoteName('location'), + ]) + ->from($db->quoteName('#__update_sites')); + $db->setQuery($query); + $sites = $db->loadObjectList('update_site_id') ?: []; + + $backupFile = JPATH_ADMINISTRATOR . '/cache/mokowaas_dlkeys.json'; + $backup = []; + + if (file_exists($backupFile)) + { + $backup = json_decode(file_get_contents($backupFile), true) ?: []; + } + + $restored = 0; + $updated = false; + + foreach ($sites as $id => $site) + { + $currentKey = trim((string) $site->extra_query); + $backupKey = $backup[$id] ?? ''; + + if ($currentKey !== '') + { + // Site has a key — update backup if changed + if ($currentKey !== $backupKey) + { + $backup[$id] = $currentKey; + $updated = true; + } + } + elseif ($backupKey !== '') + { + // Key was wiped — restore from backup + $db->setQuery( + $db->getQuery(true) + ->update($db->quoteName('#__update_sites')) + ->set($db->quoteName('extra_query') . ' = ' . $db->quote($backupKey)) + ->where($db->quoteName('update_site_id') . ' = ' . (int) $id) + )->execute(); + + $restored++; + } + } + + // Clean up backup entries for update sites that no longer exist + $currentIds = array_keys($sites); + + foreach (array_keys($backup) as $backupId) + { + if (!isset($sites[$backupId])) + { + unset($backup[$backupId]); + $updated = true; + } + } + + if ($updated || $restored > 0) + { + file_put_contents($backupFile, json_encode($backup, JSON_PRETTY_PRINT)); + } + + if ($restored > 0) + { + Log::add( + sprintf('MokoWaaS: restored %d download key(s) that were cleared by Joomla.', $restored), + Log::INFO, + 'mokowaas' + ); + } + } + catch (\Throwable $e) + { + // Non-critical — don't break the site over key backup + } + } }