feat: extend snapshots to include custom fields and tags (#57)
When articles are included in a snapshot, now also captures: - #__tags (tag definitions) - #__fields (custom field definitions for com_content.article) - #__fields_values (custom field values) - #__fields_categories (field-to-category mappings) Restore correctly scopes deletes to avoid touching non-content fields. Closes #57
This commit is contained in:
@@ -41,6 +41,10 @@ class SnapshotEngine
|
||||
private const ARTICLE_RELATED = [
|
||||
'#__workflow_associations',
|
||||
'#__contentitem_tag_map',
|
||||
'#__tags',
|
||||
'#__fields',
|
||||
'#__fields_values',
|
||||
'#__fields_categories',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -107,6 +111,32 @@ class SnapshotEngine
|
||||
$rows = $this->dumpTagMap($db, $prefix);
|
||||
$data['tables']['#__contentitem_tag_map'] = $rows;
|
||||
$this->log(' #__contentitem_tag_map: ' . count($rows) . ' rows');
|
||||
|
||||
// Tags — dump all (shared, small table)
|
||||
$rows = $this->dumpTable($db, str_replace('#__', $prefix, '#__tags'), '#__tags', 'articles');
|
||||
$data['tables']['#__tags'] = $rows;
|
||||
$this->log(' #__tags: ' . count($rows) . ' rows');
|
||||
|
||||
// Custom fields — only com_content.article context
|
||||
$rows = $this->dumpFilteredTable(
|
||||
$db,
|
||||
str_replace('#__', $prefix, '#__fields'),
|
||||
'#__fields',
|
||||
'context',
|
||||
'com_content.article'
|
||||
);
|
||||
$data['tables']['#__fields'] = $rows;
|
||||
$this->log(' #__fields: ' . count($rows) . ' rows');
|
||||
|
||||
// Field values — dump all (small, article-scoped)
|
||||
$rows = $this->dumpTable($db, str_replace('#__', $prefix, '#__fields_values'), '#__fields_values', 'articles');
|
||||
$data['tables']['#__fields_values'] = $rows;
|
||||
$this->log(' #__fields_values: ' . count($rows) . ' rows');
|
||||
|
||||
// Field-category mappings — only for com_content.article fields
|
||||
$rows = $this->dumpFieldCategories($db, $prefix);
|
||||
$data['tables']['#__fields_categories'] = $rows;
|
||||
$this->log(' #__fields_categories: ' . count($rows) . ' rows');
|
||||
}
|
||||
|
||||
// Count items
|
||||
@@ -231,6 +261,30 @@ class SnapshotEngine
|
||||
return $db->loadAssocList() ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump field-category mappings for com_content.article fields.
|
||||
*
|
||||
* Uses a subquery: field_id IN (SELECT id FROM #__fields WHERE context = 'com_content.article')
|
||||
*/
|
||||
private function dumpFieldCategories(object $db, string $prefix): array
|
||||
{
|
||||
$fcTable = $prefix . 'fields_categories';
|
||||
$fTable = $prefix . 'fields';
|
||||
|
||||
$subQuery = $db->getQuery(true)
|
||||
->select($db->quoteName('id'))
|
||||
->from($db->quoteName($fTable))
|
||||
->where($db->quoteName('context') . ' = ' . $db->quote('com_content.article'));
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName($fcTable))
|
||||
->where($db->quoteName('field_id') . ' IN (' . $subQuery . ')');
|
||||
$db->setQuery($query);
|
||||
|
||||
return $db->loadAssocList() ?: [];
|
||||
}
|
||||
|
||||
private function log(string $message): void
|
||||
{
|
||||
$this->log[] = '[' . date('H:i:s') . '] ' . $message;
|
||||
|
||||
@@ -33,6 +33,10 @@ class SnapshotRestoreEngine
|
||||
'#__contentitem_tag_map' => null, // composite key, handled specially
|
||||
'#__modules' => 'id',
|
||||
'#__modules_menu' => null, // composite key, handled specially
|
||||
'#__tags' => 'id',
|
||||
'#__fields' => 'id',
|
||||
'#__fields_values' => null, // composite key, handled specially
|
||||
'#__fields_categories' => null, // composite key, handled specially
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -282,6 +286,40 @@ class SnapshotRestoreEngine
|
||||
$query->where($db->quoteName('moduleid') . ' IN (' . implode(',', $moduleIds) . ')');
|
||||
break;
|
||||
|
||||
case '#__tags':
|
||||
// Only delete tags that exist in the snapshot — never wipe all tags
|
||||
$ids = array_filter(array_column($rows, 'id'));
|
||||
|
||||
if (empty($ids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ids = array_map('intval', $ids);
|
||||
$query->where($db->quoteName('id') . ' IN (' . implode(',', $ids) . ')');
|
||||
break;
|
||||
|
||||
case '#__fields':
|
||||
// Only delete custom fields scoped to com_content.article
|
||||
$query->where($db->quoteName('context') . ' = ' . $db->quote('com_content.article'));
|
||||
break;
|
||||
|
||||
case '#__fields_values':
|
||||
// Delete all field values — they are article-scoped
|
||||
break;
|
||||
|
||||
case '#__fields_categories':
|
||||
// Delete field-category mappings for com_content.article fields only
|
||||
$prefix = $db->getPrefix();
|
||||
$fTable = $prefix . 'fields';
|
||||
|
||||
$subQuery = $db->getQuery(true)
|
||||
->select($db->quoteName('id'))
|
||||
->from($db->quoteName($fTable))
|
||||
->where($db->quoteName('context') . ' = ' . $db->quote('com_content.article'));
|
||||
|
||||
$query->where($db->quoteName('field_id') . ' IN (' . $subQuery . ')');
|
||||
break;
|
||||
|
||||
// #__content and #__content_frontpage are fully owned by com_content
|
||||
default:
|
||||
break;
|
||||
@@ -303,6 +341,10 @@ class SnapshotRestoreEngine
|
||||
$tables[] = '#__content_frontpage';
|
||||
$tables[] = '#__workflow_associations';
|
||||
$tables[] = '#__contentitem_tag_map';
|
||||
$tables[] = '#__tags';
|
||||
$tables[] = '#__fields';
|
||||
$tables[] = '#__fields_values';
|
||||
$tables[] = '#__fields_categories';
|
||||
}
|
||||
|
||||
if (in_array('categories', $types)) {
|
||||
|
||||
Reference in New Issue
Block a user