From 679789e2678722c734e53b3017fe4cc817507f0b Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 21 Jun 2026 08:55:28 -0500 Subject: [PATCH] fix(install): relocate stale plugin files instead of deleting them When plugins install to the group root due to empty element, postflight now moves the files to the correct plugin subdirectory and fixes the element value in the DB. This preserves the plugin files instead of losing them on every update. --- source/script.php | 78 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/source/script.php b/source/script.php index bfe0834b..9170c64b 100644 --- a/source/script.php +++ b/source/script.php @@ -526,36 +526,82 @@ class Pkg_MokosuiteclientInstallerScript try { $db = Factory::getDbo(); + $systemDir = JPATH_PLUGINS . '/system'; - // Delete orphaned extension rows with empty element - $db->setQuery("DELETE FROM " . $db->quoteName('#__extensions') - . " WHERE " . $db->quoteName('element') . " = ''"); - $db->execute(); - $deleted = $db->getAffectedRows(); + // Find empty-element rows and fix them using the manifest name + $db->setQuery( + $db->getQuery(true) + ->select([$db->quoteName('extension_id'), $db->quoteName('name'), $db->quoteName('folder')]) + ->from($db->quoteName('#__extensions')) + ->where($db->quoteName('element') . " = ''") + ->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) + ); + $orphans = $db->loadObjectList() ?: []; - if ($deleted > 0) + foreach ($orphans as $orphan) { - Log::add("Deleted {$deleted} orphaned extension row(s) with empty element", Log::INFO, 'mokosuiteclient'); - Factory::getApplication()->enqueueMessage( - sprintf('Cleaned up %d orphaned extension record(s).', $deleted), - 'message' - ); + // Derive element from the manifest name (e.g. "plg_system_mokosuiteclient_offline" → "mokosuiteclient_offline") + $element = preg_replace('/^plg_[a-z]+_/', '', strtolower($orphan->name)); + + // Also try matching from stale XML manifests at group root + if (empty($element)) + { + continue; + } + + $pluginDir = $systemDir . '/' . $element; + + // If plugin dir doesn't exist but stale files do, relocate them + if (!is_dir($pluginDir) && $orphan->folder === 'system') + { + @mkdir($pluginDir, 0755, true); + + // Move stale dirs (services, src, language) into the plugin dir + foreach (['services', 'src', 'language'] as $subDir) + { + $stalePath = $systemDir . '/' . $subDir; + + if (is_dir($stalePath) && !is_dir($pluginDir . '/' . $subDir)) + { + @rename($stalePath, $pluginDir . '/' . $subDir); + } + } + + // Move stale manifest XML + $staleXml = $systemDir . '/' . $element . '.xml'; + + if (is_file($staleXml)) + { + @rename($staleXml, $pluginDir . '/' . $element . '.xml'); + } + + Log::add("Relocated stale files to plugins/system/{$element}/", Log::INFO, 'mokosuiteclient'); + } + + // Fix the element in the DB + $db->setQuery( + $db->getQuery(true) + ->update($db->quoteName('#__extensions')) + ->set($db->quoteName('element') . ' = ' . $db->quote($element)) + ->where($db->quoteName('extension_id') . ' = ' . (int) $orphan->extension_id) + )->execute(); + + Log::add("Fixed empty element → {$element} (ID {$orphan->extension_id})", Log::INFO, 'mokosuiteclient'); } - // Remove stale plugin files that leaked to group root + // Clean up any remaining stale dirs that couldn't be matched foreach (['services', 'src', 'language'] as $dir) { - $path = JPATH_PLUGINS . '/system/' . $dir; + $path = $systemDir . '/' . $dir; if (is_dir($path)) { $this->rmdirRecursive($path); - Log::add("Removed stale directory: plugins/system/{$dir}", Log::INFO, 'mokosuiteclient'); } } - // Remove stale XML manifests at group root - foreach (glob(JPATH_PLUGINS . '/system/mokosuiteclient_*.xml') ?: [] as $staleXml) + // Clean up stale XML manifests at group root + foreach (glob($systemDir . '/mokosuiteclient_*.xml') ?: [] as $staleXml) { @unlink($staleXml); }