diff --git a/source/script.php b/source/script.php index 98c5b85c..d920a019 100644 --- a/source/script.php +++ b/source/script.php @@ -62,8 +62,12 @@ class Pkg_MokosuiteclientInstallerScript } } + /** @var \Joomla\CMS\Installer\InstallerAdapter|null */ + private $installerParent = null; + public function postflight($type, $parent) { + $this->installerParent = $parent; // Migrate MokoWaaS database tables to MokoSuiteClient naming $this->migrateWaasTables(); @@ -527,20 +531,29 @@ class Pkg_MokosuiteclientInstallerScript { $db = Factory::getDbo(); - // Delete orphaned extension rows with empty element — they'll be - // recreated correctly on the next package update + // 1. Delete orphaned extension rows with empty element $db->setQuery("DELETE FROM " . $db->quoteName('#__extensions') . " WHERE " . $db->quoteName('element') . " = ''" . " AND " . $db->quoteName('type') . " = 'plugin'"); $db->execute(); $deleted = $db->getAffectedRows(); + // Also delete rows where element is the display name (spaces) + $db->setQuery( + $db->getQuery(true) + ->delete($db->quoteName('#__extensions')) + ->where($db->quoteName('element') . ' LIKE ' . $db->quote('% %')) + ->where($db->quoteName('element') . ' LIKE ' . $db->quote('%mokosuiteclient%')) + ); + $db->execute(); + $deleted += $db->getAffectedRows(); + if ($deleted > 0) { - Log::add("Deleted {$deleted} orphaned plugin row(s) with empty element", Log::INFO, 'mokosuiteclient'); + Log::add("Deleted {$deleted} orphaned plugin row(s)", Log::INFO, 'mokosuiteclient'); } - // Clean up stale plugin files that leaked to plugin group roots + // 2. Clean up stale plugin files that leaked to group roots $groupDirs = [JPATH_PLUGINS . '/system', JPATH_PLUGINS . '/task', JPATH_PLUGINS . '/webservices']; foreach ($groupDirs as $groupDir) @@ -552,7 +565,6 @@ class Pkg_MokosuiteclientInstallerScript if (is_dir($path)) { $this->rmdirRecursive($path); - Log::add("Removed stale: {$path}", Log::INFO, 'mokosuiteclient'); } } @@ -562,20 +574,93 @@ class Pkg_MokosuiteclientInstallerScript @unlink($staleXml); } - // Remove dirs with spaces (Joomla uses display name as dir when element is empty) + // Remove dirs with spaces (Joomla uses display name as dir) foreach (glob($groupDir . '/*mokosuiteclient*', GLOB_ONLYDIR) ?: [] as $badDir) { if (strpos(basename($badDir), ' ') !== false) { $this->rmdirRecursive($badDir); - Log::add("Removed bad dir: " . basename($badDir), Log::INFO, 'mokosuiteclient'); } } } + + // 3. Reinstall plugins that are missing their directory + $this->reinstallBrokenPlugins(); + } + catch (\Throwable $e) + { + Log::add('Empty element cleanup error: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient'); + } + } + + /** + * Reinstall plugins whose files are missing from disk. + * + * Uses the sub-extension zip files from the package source directory + * (still available during postflight) to reinstall any plugin that + * doesn't have its directory on disk. + */ + private function reinstallBrokenPlugins(): void + { + if (!$this->installerParent) + { + return; + } + + try + { + $installer = $this->installerParent->getParent(); + $sourceDir = $installer->getPath('source'); + + if (empty($sourceDir) || !is_dir($sourceDir . '/packages')) + { + return; + } + + // Plugins that should exist on disk + $expected = [ + 'system' => ['mokosuiteclient_offline', 'mokosuiteclient_firewall', 'mokosuiteclient_tenant', 'mokosuiteclient_devtools', 'mokosuiteclient_dbip'], + 'task' => ['mokosuiteclient_tickets'], + ]; + + foreach ($expected as $group => $elements) + { + foreach ($elements as $element) + { + $pluginDir = JPATH_PLUGINS . '/' . $group . '/' . $element; + + if (is_dir($pluginDir)) + { + continue; // Already installed correctly + } + + $zipName = 'plg_' . $group . '_' . $element . '.zip'; + $zipPath = $sourceDir . '/packages/' . $zipName; + + if (!is_file($zipPath)) + { + continue; + } + + // Extract the zip to the correct plugin directory + $zip = new \ZipArchive(); + + if ($zip->open($zipPath) !== true) + { + continue; + } + + @mkdir($pluginDir, 0755, true); + $zip->extractTo($pluginDir); + $zip->close(); + + Log::add("Reinstalled {$group}/{$element} from package zip", Log::INFO, 'mokosuiteclient'); + } + } } catch (\Throwable $e) { - Log::add('Empty element cleanup error: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient'); + Log::add('Plugin reinstall error: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient'); } }