diff --git a/src/packages/com_mokowaas/api/src/Controller/ResetController.php b/src/packages/com_mokowaas/api/src/Controller/ResetController.php index 671332f..7d88c7d 100644 --- a/src/packages/com_mokowaas/api/src/Controller/ResetController.php +++ b/src/packages/com_mokowaas/api/src/Controller/ResetController.php @@ -103,11 +103,13 @@ class ResetController extends BaseController require_once $serviceFile; - $tablesRaw = $params->get('demo_snapshot_tables', ''); - $tables = array_filter(array_map('trim', explode("\n", $tablesRaw))); - $media = (bool) $params->get('demo_snapshot_include_media', 1); + $tablesParam = $params->get('demo_snapshot_tables', ''); + $tables = is_array($tablesParam) ? array_filter($tablesParam) : array_filter(array_map('trim', explode("\n", $tablesParam))); + $media = $params->get('demo_snapshot_include_media', ['images']); + if ($media === '1' || $media === true) $media = ['images']; + if ($media === '0' || $media === false) $media = []; - return new \Moko\Plugin\System\MokoWaaS\Service\DemoResetService($tables, $media); + return new \Moko\Plugin\System\MokoWaaS\Service\DemoResetService($tables, (array) $media); } /** diff --git a/src/packages/com_mokowaas/api/src/Controller/SnapshotController.php b/src/packages/com_mokowaas/api/src/Controller/SnapshotController.php index 2577cb7..b6cf8b4 100644 --- a/src/packages/com_mokowaas/api/src/Controller/SnapshotController.php +++ b/src/packages/com_mokowaas/api/src/Controller/SnapshotController.php @@ -130,11 +130,13 @@ class SnapshotController extends BaseController $plugin = PluginHelper::getPlugin('system', 'mokowaas'); $params = $plugin ? new Registry($plugin->params) : new Registry; - $tablesRaw = $params->get('demo_snapshot_tables', ''); - $tables = array_filter(array_map('trim', explode("\n", $tablesRaw))); - $media = (bool) $params->get('demo_snapshot_include_media', 1); + $tablesParam = $params->get('demo_snapshot_tables', ''); + $tables = is_array($tablesParam) ? array_filter($tablesParam) : array_filter(array_map('trim', explode("\n", $tablesParam))); + $media = $params->get('demo_snapshot_include_media', ['images']); + if ($media === '1' || $media === true) $media = ['images']; + if ($media === '0' || $media === false) $media = []; - return new \Moko\Plugin\System\MokoWaaS\Service\DemoResetService($tables, $media); + return new \Moko\Plugin\System\MokoWaaS\Service\DemoResetService($tables, (array) $media); } /** diff --git a/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php b/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php index e6a26c9..835b0f0 100644 --- a/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php +++ b/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php @@ -1734,16 +1734,33 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface { require_once __DIR__ . '/../Service/DemoResetService.php'; - $tablesRaw = $this->params->get('demo_snapshot_tables', ''); - $tables = array_filter( - array_map('trim', explode("\n", $tablesRaw)) - ); + $tablesParam = $this->params->get('demo_snapshot_tables', ''); - $includeMedia = (bool) $this->params->get('demo_snapshot_include_media', 1); + // Handle both checkbox array and legacy newline-separated textarea + if (is_array($tablesParam)) + { + $tables = array_filter($tablesParam); + } + else + { + $tables = array_filter(array_map('trim', explode("\n", $tablesParam))); + } + + $mediaDirs = $this->params->get('demo_snapshot_include_media', ['images']); + + // Handle legacy boolean value + if ($mediaDirs === '1' || $mediaDirs === true) + { + $mediaDirs = ['images']; + } + elseif ($mediaDirs === '0' || $mediaDirs === false) + { + $mediaDirs = []; + } return new \Moko\Plugin\System\MokoWaaS\Service\DemoResetService( $tables, - $includeMedia + (array) $mediaDirs ); } diff --git a/src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php b/src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php new file mode 100644 index 0000000..91965db --- /dev/null +++ b/src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php @@ -0,0 +1,157 @@ + ['content', 'categories', 'fields', 'tags', 'contentitem_tag_map', 'ucm_content', 'ucm_history'], + 'users' => ['users', 'user_usergroup_map', 'user_profiles', 'usergroups', 'user_keys', 'user_mfa'], + 'menus' => ['menu', 'menu_types'], + 'modules' => ['modules', 'modules_menu'], + 'assets' => ['assets'], + ]; + + protected function getOptions() + { + $db = Factory::getDbo(); + $prefix = $db->getPrefix(); + $tables = $db->getTableList(); + + $options = []; + + foreach ($tables as $table) + { + // Only show tables with the site's prefix + if (strpos($table, $prefix) !== 0) + { + continue; + } + + // Convert real table name to #__ notation + $logical = '#__' . substr($table, strlen($prefix)); + + // Determine group for display ordering + $group = 'Other'; + + foreach (self::TABLE_GROUPS as $groupName => $patterns) + { + $suffix = substr($table, strlen($prefix)); + + foreach ($patterns as $pattern) + { + if ($suffix === $pattern) + { + $group = ucfirst($groupName); + break 2; + } + } + } + + $obj = (object) [ + 'value' => $logical, + 'text' => $logical, + 'disable' => false, + 'class' => '', + 'onclick' => '', + ]; + + $options[$group][] = $obj; + } + + // Flatten with group headers: content tables first, then alphabetical + $priority = ['Content', 'Users', 'Menus', 'Modules', 'Assets']; + $sorted = []; + + foreach ($priority as $g) + { + if (isset($options[$g])) + { + $sorted = array_merge($sorted, $options[$g]); + unset($options[$g]); + } + } + + // Remaining tables (Other) + if (isset($options['Other'])) + { + sort($options['Other']); + $sorted = array_merge($sorted, $options['Other']); + } + + return $sorted; + } + + protected function getInput() + { + // If no value stored yet, use defaults + if ($this->value === null || $this->value === '') + { + $this->value = self::DEFAULT_TABLES; + } + elseif (is_string($this->value)) + { + // Handle legacy textarea format (newline-separated) + $this->value = array_filter(array_map('trim', explode("\n", $this->value))); + } + + return parent::getInput(); + } +} diff --git a/src/packages/plg_system_mokowaas/Service/DemoResetService.php b/src/packages/plg_system_mokowaas/Service/DemoResetService.php index 13fc439..33218cc 100644 --- a/src/packages/plg_system_mokowaas/Service/DemoResetService.php +++ b/src/packages/plg_system_mokowaas/Service/DemoResetService.php @@ -91,27 +91,39 @@ class DemoResetService private array $tables; /** - * Whether to include media files in snapshots. + * Directories to include in media snapshot (e.g. ['images', 'media']). * - * @var bool - * @since 02.21.00 + * @var array + * @since 02.25.00 */ - private bool $includeMedia; + private array $mediaDirs; /** * Constructor. * - * @param array $tables Table names with #__ prefix - * @param bool $includeMedia Include /images/ directory in snapshot - * @param string $baseDir Override snapshot root (for testing) + * @param array $tables Table names with #__ prefix + * @param array|bool $mediaDirs Dirs to snapshot: ['images','media'], true (= images), false/[] (= none) + * @param string $baseDir Override snapshot root (for testing) * * @since 02.21.00 */ - public function __construct(array $tables = [], bool $includeMedia = true, string $baseDir = '') + public function __construct(array $tables = [], $mediaDirs = ['images'], string $baseDir = '') { - $this->tables = !empty($tables) ? $tables : self::DEFAULT_TABLES; - $this->includeMedia = $includeMedia; - $this->snapshotDir = $baseDir ?: JPATH_ROOT . '/mokowaas-snapshots'; + $this->tables = !empty($tables) ? $tables : self::DEFAULT_TABLES; + $this->snapshotDir = $baseDir ?: JPATH_ROOT . '/mokowaas-snapshots'; + + if ($mediaDirs === true) + { + $this->mediaDirs = ['images']; + } + elseif ($mediaDirs === false || empty($mediaDirs)) + { + $this->mediaDirs = []; + } + else + { + $this->mediaDirs = (array) $mediaDirs; + } } /** @@ -193,12 +205,22 @@ class DemoResetService $dumped++; } - // Media snapshot - $hasMedia = false; + // Media snapshot — one ZIP per directory + $mediaDirs = []; - if ($this->includeMedia) + foreach ($this->mediaDirs as $dir) { - $hasMedia = $this->snapshotMedia($path); + $fullPath = JPATH_ROOT . '/' . $dir; + + if (is_dir($fullPath)) + { + $zipName = 'media_' . $dir . '.zip'; + + if ($this->snapshotDirectory($fullPath, $path . '/' . $zipName)) + { + $mediaDirs[] = $dir; + } + } } // Write manifest @@ -207,7 +229,8 @@ class DemoResetService 'created_at' => gmdate('Y-m-d\TH:i:s\Z'), 'tables' => $dumped, 'table_list' => $this->tables, - 'has_media' => $hasMedia, + 'has_media' => !empty($mediaDirs), + 'media_dirs' => $mediaDirs, 'joomla_version' => JVERSION, ]; @@ -308,12 +331,41 @@ class DemoResetService } } - // Restore media + // Restore media directories $mediaRestored = false; + $restoredDirs = $manifest['media_dirs'] ?? []; - if ($manifest['has_media'] ?? false) + // Legacy support: old manifests used has_media=true with a single media.zip for /images/ + if (empty($restoredDirs) && ($manifest['has_media'] ?? false)) { - $mediaRestored = $this->restoreMedia($path); + $restoredDirs = ['images']; + } + + foreach ($restoredDirs as $dir) + { + $zipName = 'media_' . $dir . '.zip'; + $zipPath = $path . '/' . $zipName; + + // Legacy fallback: old snapshots used media.zip for images + if (!file_exists($zipPath) && $dir === 'images' && file_exists($path . '/media.zip')) + { + $zipPath = $path . '/media.zip'; + } + + if (file_exists($zipPath)) + { + $targetDir = JPATH_ROOT . '/' . $dir; + $this->clearDirectory($targetDir); + + $zip = new \ZipArchive(); + + if ($zip->open($zipPath) === true) + { + $zip->extractTo($targetDir); + $zip->close(); + $mediaRestored = true; + } + } } Log::add( @@ -495,25 +547,23 @@ class DemoResetService } /** - * Create a ZIP archive of the /images/ directory. + * Create a ZIP archive of a directory. * - * @param string $snapshotDir Snapshot directory path + * @param string $sourceDir Full path to the directory to archive + * @param string $zipPath Full path for the output ZIP file * - * @return bool True if media was archived + * @return bool True if archived successfully * - * @since 02.21.00 + * @since 02.25.00 */ - private function snapshotMedia(string $snapshotDir): bool + private function snapshotDirectory(string $sourceDir, string $zipPath): bool { - $imagesDir = JPATH_ROOT . '/images'; - - if (!is_dir($imagesDir)) + if (!is_dir($sourceDir)) { return false; } - $zipPath = $snapshotDir . '/media.zip'; - $zip = new \ZipArchive(); + $zip = new \ZipArchive(); if ($zip->open($zipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { @@ -521,13 +571,13 @@ class DemoResetService } $iterator = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($imagesDir, \RecursiveDirectoryIterator::SKIP_DOTS), + new \RecursiveDirectoryIterator($sourceDir, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $item) { - $relativePath = substr($item->getPathname(), strlen($imagesDir) + 1); + $relativePath = substr($item->getPathname(), strlen($sourceDir) + 1); $relativePath = str_replace('\\', '/', $relativePath); if ($item->isDir()) @@ -545,41 +595,6 @@ class DemoResetService return true; } - /** - * Restore media files from a snapshot ZIP. - * - * @param string $snapshotDir Snapshot directory path - * - * @return bool True if media was restored - * - * @since 02.21.00 - */ - private function restoreMedia(string $snapshotDir): bool - { - $zipPath = $snapshotDir . '/media.zip'; - $imagesDir = JPATH_ROOT . '/images'; - - if (!file_exists($zipPath)) - { - return false; - } - - // Clear existing images directory contents (keep the directory itself) - $this->clearDirectory($imagesDir); - - $zip = new \ZipArchive(); - - if ($zip->open($zipPath) !== true) - { - return false; - } - - $zip->extractTo($imagesDir); - $zip->close(); - - return true; - } - /** * Ensure the snapshot root directory exists with .htaccess protection. * diff --git a/src/packages/plg_system_mokowaas/language/en-GB/plg_system_mokowaas.ini b/src/packages/plg_system_mokowaas/language/en-GB/plg_system_mokowaas.ini index 8bd0219..e9fc2f3 100644 --- a/src/packages/plg_system_mokowaas/language/en-GB/plg_system_mokowaas.ini +++ b/src/packages/plg_system_mokowaas/language/en-GB/plg_system_mokowaas.ini @@ -171,8 +171,8 @@ PLG_SYSTEM_MOKOWAAS_DEMO_NEXT_RESET_LABEL="Next Scheduled Reset" PLG_SYSTEM_MOKOWAAS_DEMO_NEXT_RESET_DESC="Calculated automatically from the reset schedule. The banner countdown uses this timestamp." PLG_SYSTEM_MOKOWAAS_DEMO_TABLES_LABEL="Snapshot Tables" PLG_SYSTEM_MOKOWAAS_DEMO_TABLES_DESC="Database tables to include in snapshots. One per line, using #__ prefix. These tables will be truncated and restored during a reset." -PLG_SYSTEM_MOKOWAAS_DEMO_MEDIA_LABEL="Include Media Files" -PLG_SYSTEM_MOKOWAAS_DEMO_MEDIA_DESC="Include the /images/ directory in snapshots. Disabling this speeds up snapshot/restore for sites with large media libraries." +PLG_SYSTEM_MOKOWAAS_DEMO_MEDIA_LABEL="Include Directories" +PLG_SYSTEM_MOKOWAAS_DEMO_MEDIA_DESC="Select which directories to include in the snapshot. Images contains uploaded media, Media contains extension assets." PLG_SYSTEM_MOKOWAAS_DEMO_ACTIVE_BASELINE_LABEL="Active Baseline Name" PLG_SYSTEM_MOKOWAAS_DEMO_ACTIVE_BASELINE_DESC="Name of the baseline snapshot used by admin toggles and scheduled tasks. Alphanumeric, hyphens, and underscores only." PLG_SYSTEM_MOKOWAAS_DEMO_TAKE_SNAPSHOT_LABEL="Take Snapshot Now" diff --git a/src/packages/plg_system_mokowaas/language/en-US/plg_system_mokowaas.ini b/src/packages/plg_system_mokowaas/language/en-US/plg_system_mokowaas.ini index 8bd0219..e9fc2f3 100644 --- a/src/packages/plg_system_mokowaas/language/en-US/plg_system_mokowaas.ini +++ b/src/packages/plg_system_mokowaas/language/en-US/plg_system_mokowaas.ini @@ -171,8 +171,8 @@ PLG_SYSTEM_MOKOWAAS_DEMO_NEXT_RESET_LABEL="Next Scheduled Reset" PLG_SYSTEM_MOKOWAAS_DEMO_NEXT_RESET_DESC="Calculated automatically from the reset schedule. The banner countdown uses this timestamp." PLG_SYSTEM_MOKOWAAS_DEMO_TABLES_LABEL="Snapshot Tables" PLG_SYSTEM_MOKOWAAS_DEMO_TABLES_DESC="Database tables to include in snapshots. One per line, using #__ prefix. These tables will be truncated and restored during a reset." -PLG_SYSTEM_MOKOWAAS_DEMO_MEDIA_LABEL="Include Media Files" -PLG_SYSTEM_MOKOWAAS_DEMO_MEDIA_DESC="Include the /images/ directory in snapshots. Disabling this speeds up snapshot/restore for sites with large media libraries." +PLG_SYSTEM_MOKOWAAS_DEMO_MEDIA_LABEL="Include Directories" +PLG_SYSTEM_MOKOWAAS_DEMO_MEDIA_DESC="Select which directories to include in the snapshot. Images contains uploaded media, Media contains extension assets." PLG_SYSTEM_MOKOWAAS_DEMO_ACTIVE_BASELINE_LABEL="Active Baseline Name" PLG_SYSTEM_MOKOWAAS_DEMO_ACTIVE_BASELINE_DESC="Name of the baseline snapshot used by admin toggles and scheduled tasks. Alphanumeric, hyphens, and underscores only." PLG_SYSTEM_MOKOWAAS_DEMO_TAKE_SNAPSHOT_LABEL="Take Snapshot Now" diff --git a/src/packages/plg_system_mokowaas/mokowaas.xml b/src/packages/plg_system_mokowaas/mokowaas.xml index 24960fa..b02504e 100644 --- a/src/packages/plg_system_mokowaas/mokowaas.xml +++ b/src/packages/plg_system_mokowaas/mokowaas.xml @@ -268,6 +268,7 @@
- - + - - + description="PLG_SYSTEM_MOKOWAAS_DEMO_MEDIA_DESC"> + + get('demo_snapshot_tables', ''); - $tables = array_filter(array_map('trim', explode("\n", $tablesRaw))); - $media = (bool) $sysParams->get('demo_snapshot_include_media', 1); + $tablesParam = $sysParams->get('demo_snapshot_tables', ''); + $tables = is_array($tablesParam) ? array_filter($tablesParam) : array_filter(array_map('trim', explode("\n", $tablesParam))); + $media = $sysParams->get('demo_snapshot_include_media', ['images']); + if ($media === '1' || $media === true) $media = ['images']; + if ($media === '0' || $media === false) $media = []; - $service = new \Moko\Plugin\System\MokoWaaS\Service\DemoResetService($tables, $media); + $service = new \Moko\Plugin\System\MokoWaaS\Service\DemoResetService($tables, (array) $media); try {