fix(installer): self-heal orphaned com_component-mokosuitebackup on update
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 16s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 3s
Universal: PR Check / Require Docs Update (pull_request) Failing after 7s
Universal: PR Check / Secret Scan (pull_request) Successful in 9s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 52s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 50s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 29s
Universal: Build & Release / Promote to RC (pull_request) Failing after 15s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 16s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 3s
Universal: PR Check / Require Docs Update (pull_request) Failing after 7s
Universal: PR Check / Secret Scan (pull_request) Successful in 9s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 52s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 50s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 29s
Universal: Build & Release / Promote to RC (pull_request) Failing after 15s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
The old "Type - Name" component <name> made Joomla register a duplicate component under element com_component-mokosuitebackup (its own extension row, admin menu, schema rows and administrator/ folder), orphaning the real com_mokosuitebackup on the old version. The manifest fix (#213) prevents new occurrences; this reconciles already-affected sites automatically. Package postflight now removes any component whose element matches %component%mokosuitebackup% (excluding com_mokosuitebackup): deletes its #__menu, #__schemas and #__update_sites_extensions rows, the #__extensions record, and the stray admin folder. Idempotent and non-fatal. Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
This commit is contained in:
@@ -172,6 +172,11 @@ class Pkg_MokoSuiteBackupInstallerScript
|
||||
/* Migrate profiles with old default backup_dir values to [DEFAULT_DIR] placeholder */
|
||||
$this->migrateDefaultBackupDir();
|
||||
|
||||
/* Remove the orphaned mis-registered component (element
|
||||
com_component-mokosuitebackup) left behind when the component <name>
|
||||
briefly used the "Type - Name" convention. Self-healing on every update. */
|
||||
$this->removeOrphanedComponent();
|
||||
|
||||
/* Install completion notice (install and update) */
|
||||
$this->installSuccessful();
|
||||
|
||||
@@ -192,6 +197,107 @@ class Pkg_MokoSuiteBackupInstallerScript
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the orphaned component registration created when the component
|
||||
* <name> briefly used the "Type - Name" display convention. Joomla derives a
|
||||
* component's element from its <name>, so "Component - MokoSuiteBackup"
|
||||
* produced element com_component-mokosuitebackup — its own extension record,
|
||||
* admin menu items, schema rows and administrator/ folder — orphaning the real
|
||||
* com_mokosuitebackup on the old version. This reconciles affected sites on
|
||||
* update. Idempotent and non-fatal: does nothing when no orphan exists.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function removeOrphanedComponent(): void
|
||||
{
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName(['extension_id', 'element']))
|
||||
->from($db->quoteName('#__extensions'))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
|
||||
->where($db->quoteName('client_id') . ' = 1')
|
||||
->where($db->quoteName('element') . ' LIKE ' . $db->quote('%component%mokosuitebackup%'))
|
||||
->where($db->quoteName('element') . ' <> ' . $db->quote('com_mokosuitebackup'));
|
||||
$db->setQuery($query);
|
||||
$orphans = $db->loadObjectList();
|
||||
|
||||
if (empty($orphans)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($orphans as $orphan) {
|
||||
$id = (int) $orphan->extension_id;
|
||||
|
||||
/* Admin menu items owned by the orphan component */
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->delete($db->quoteName('#__menu'))
|
||||
->where($db->quoteName('component_id') . ' = ' . $id)
|
||||
)->execute();
|
||||
|
||||
/* Schema-version rows */
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->delete($db->quoteName('#__schemas'))
|
||||
->where($db->quoteName('extension_id') . ' = ' . $id)
|
||||
)->execute();
|
||||
|
||||
/* Update-site mapping (leave #__update_sites; harmless if orphaned) */
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->delete($db->quoteName('#__update_sites_extensions'))
|
||||
->where($db->quoteName('extension_id') . ' = ' . $id)
|
||||
)->execute();
|
||||
|
||||
/* The bogus extension record itself */
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->delete($db->quoteName('#__extensions'))
|
||||
->where($db->quoteName('extension_id') . ' = ' . $id)
|
||||
)->execute();
|
||||
|
||||
/* The stray admin component folder on disk */
|
||||
$this->deleteFolderRecursive(JPATH_ADMINISTRATOR . '/components/' . $orphan->element);
|
||||
|
||||
Factory::getApplication()->enqueueMessage(
|
||||
'MokoSuiteBackup: removed orphaned component registration "'
|
||||
. htmlspecialchars($orphan->element, ENT_QUOTES, 'UTF-8') . '".',
|
||||
'info'
|
||||
);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
/* Never block the install/update over best-effort cleanup */
|
||||
Log::add('MokoSuiteBackup orphan-component cleanup failed: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively delete a directory (version-independent, no Folder dependency).
|
||||
*
|
||||
* @param string $dir Absolute path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function deleteFolderRecursive(string $dir): void
|
||||
{
|
||||
if ($dir === '' || !is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a cryptographically random webcron secret word on fresh install.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user