From 1ea9406f1ad27a442629a5cb73a1fe14caeb4a2b Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 30 May 2026 21:40:28 -0500 Subject: [PATCH] fix: rebuild assets after content restore to fix ACL After restoring content tables, deletes stale content-level assets (articles, categories, modules, contacts, banners) and rebuilds: - Category nested set tree - Menu nested set tree - Asset nested set tree - Re-creates missing asset entries for articles with asset_id=0 Component and extension-level assets are never touched. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Service/DemoResetService.php | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/src/packages/plg_system_mokowaas/Service/DemoResetService.php b/src/packages/plg_system_mokowaas/Service/DemoResetService.php index 8661c2b4..9a7ad50d 100644 --- a/src/packages/plg_system_mokowaas/Service/DemoResetService.php +++ b/src/packages/plg_system_mokowaas/Service/DemoResetService.php @@ -247,6 +247,9 @@ class DemoResetService } } + // Rebuild assets table to fix ACL after content restore + $this->rebuildAssets(); + Log::add(sprintf('Demo site reset (%d tables, media=%s)', $restored, $mediaRestored ? 'yes' : 'no'), Log::WARNING, 'mokowaas'); return [ @@ -276,6 +279,149 @@ class DemoResetService return true; } + /** + * Rebuild the assets table after content restore. + * + * Deletes content-related asset entries (which now have stale IDs) + * and rebuilds them using Joomla's Table classes. Extension and + * component-level assets are left untouched. + * + * @return void + */ + private function rebuildAssets(): void + { + try + { + $db = Factory::getDbo(); + + // Delete content-level assets (articles, categories, modules, etc.) + // Keep component-level and root assets intact + $contentAssetPrefixes = [ + 'com_content.article.%', + 'com_content.category.%', + 'com_contact.contact.%', + 'com_banners.banner.%', + 'com_banners.category.%', + 'com_modules.module.%', + 'com_menus.menu.%', + 'com_users.user.%', + ]; + + foreach ($contentAssetPrefixes as $prefix) + { + $db->setQuery( + $db->getQuery(true) + ->delete($db->quoteName('#__assets')) + ->where($db->quoteName('name') . ' LIKE ' . $db->quote($prefix)) + ); + $db->execute(); + } + + // Rebuild category tree (also fixes category assets) + $catTable = \Joomla\CMS\Table\Table::getInstance('Category'); + + if ($catTable) + { + $catTable->rebuild(); + } + + // Rebuild menu tree + $menuTable = \Joomla\CMS\Table\Table::getInstance('Menu'); + + if ($menuTable) + { + $menuTable->rebuild(); + } + + // Rebuild asset tree + $assetTable = \Joomla\CMS\Table\Table::getInstance('Asset'); + + if ($assetTable) + { + $assetTable->rebuild(); + } + + // Re-create assets for content items that lost theirs + $this->fixContentAssets($db); + } + catch (\Throwable $e) + { + Log::add('Asset rebuild warning: ' . $e->getMessage(), Log::WARNING, 'mokowaas'); + } + } + + /** + * Re-create missing asset entries for content items. + * + * After deleting stale assets and restoring content, some items + * may reference asset_id=0. This creates new asset rows for them. + * + * @param \Joomla\Database\DatabaseInterface $db + * + * @return void + */ + private function fixContentAssets($db): void + { + // Fix articles with missing assets + $query = $db->getQuery(true) + ->select([$db->quoteName('id'), $db->quoteName('title'), $db->quoteName('alias')]) + ->from($db->quoteName('#__content')) + ->where($db->quoteName('asset_id') . ' = 0'); + + $db->setQuery($query); + $articles = $db->loadAssocList() ?: []; + + // Find the com_content component asset as parent + $db->setQuery( + $db->getQuery(true) + ->select($db->quoteName('id')) + ->from($db->quoteName('#__assets')) + ->where($db->quoteName('name') . ' = ' . $db->quote('com_content')) + ); + $contentAssetId = (int) $db->loadResult(); + + foreach ($articles as $article) + { + $assetName = 'com_content.article.' . (int) $article['id']; + + $asset = (object) [ + 'parent_id' => $contentAssetId ?: 1, + 'lft' => 0, + 'rgt' => 0, + 'level' => 0, + 'name' => $assetName, + 'title' => $article['title'], + 'rules' => '{}', + ]; + + $db->insertObject('#__assets', $asset, 'id'); + + // Update content row with new asset_id + $db->setQuery( + $db->getQuery(true) + ->update($db->quoteName('#__content')) + ->set($db->quoteName('asset_id') . ' = ' . (int) $asset->id) + ->where($db->quoteName('id') . ' = ' . (int) $article['id']) + ); + $db->execute(); + } + + // Rebuild asset tree again after inserts + try + { + $assetTable = \Joomla\CMS\Table\Table::getInstance('Asset'); + + if ($assetTable) + { + $assetTable->rebuild(); + } + } + catch (\Throwable $e) + { + // Best effort + } + } + // ------------------------------------------------------------------ // Private helpers // ------------------------------------------------------------------