diff --git a/source/packages/com_mokosuitebackup/src/Engine/SnapshotEngine.php b/source/packages/com_mokosuitebackup/src/Engine/SnapshotEngine.php index 4ea16a3..f47b045 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/SnapshotEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/SnapshotEngine.php @@ -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; diff --git a/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php b/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php index 6bb514f..665c301 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php @@ -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)) {