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) <noreply@anthropic.com>
This commit is contained in:
Jonathan Miller
2026-05-30 21:40:28 -05:00
parent 1032074e5c
commit 1ea9406f1a
@@ -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
// ------------------------------------------------------------------