From 559db324cb1f00694677a3c2556ac865080cfdfe Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 18 Jun 2026 10:24:54 -0500 Subject: [PATCH] feat(backup): scaffold backup bridge plugin (#208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add plg_system_mokosuiteclient_backup — detects MokoSuiteBackup and collects backup status for heartbeat payloads to MokoSuiteHQ. Scaffolding includes manifest, service provider, extension class, and language files. Table column names are placeholders pending MokoSuiteBackup schema confirmation (MokoSuiteBackup#47). --- .../plg_system_mokosuiteclient_backup.ini | 13 ++ .../plg_system_mokosuiteclient_backup.sys.ini | 3 + .../mokosuiteclient_backup.xml | 48 ++++ .../services/provider.php | 34 +++ .../src/Extension/Backup.php | 219 ++++++++++++++++++ source/pkg_mokosuiteclient.xml | 1 + 6 files changed, 318 insertions(+) create mode 100644 source/packages/plg_system_mokosuiteclient_backup/language/en-GB/plg_system_mokosuiteclient_backup.ini create mode 100644 source/packages/plg_system_mokosuiteclient_backup/language/en-GB/plg_system_mokosuiteclient_backup.sys.ini create mode 100644 source/packages/plg_system_mokosuiteclient_backup/mokosuiteclient_backup.xml create mode 100644 source/packages/plg_system_mokosuiteclient_backup/services/provider.php create mode 100644 source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php diff --git a/source/packages/plg_system_mokosuiteclient_backup/language/en-GB/plg_system_mokosuiteclient_backup.ini b/source/packages/plg_system_mokosuiteclient_backup/language/en-GB/plg_system_mokosuiteclient_backup.ini new file mode 100644 index 00000000..fc4e6e46 --- /dev/null +++ b/source/packages/plg_system_mokosuiteclient_backup/language/en-GB/plg_system_mokosuiteclient_backup.ini @@ -0,0 +1,13 @@ +; MokoSuiteClient Backup Bridge Plugin +; Copyright (C) 2026 Moko Consulting. All rights reserved. +; License: GPL-3.0-or-later + +PLG_SYSTEM_MOKOSUITECLIENT_BACKUP="System - MokoSuiteClient Backup" +PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC="Detects MokoSuiteBackup and includes backup status in heartbeat payloads sent to MokoSuiteHQ." + +PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC="Backup Monitoring" +PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC_DESC="Configure backup status collection for heartbeat reporting." +PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_LABEL="Include in Heartbeat" +PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_DESC="Include MokoSuiteBackup status data in heartbeat payloads sent to MokoSuiteHQ." +PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_LABEL="Stale Backup Threshold (days)" +PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_DESC="Number of days without a backup before status is marked as degraded. Default: 7." diff --git a/source/packages/plg_system_mokosuiteclient_backup/language/en-GB/plg_system_mokosuiteclient_backup.sys.ini b/source/packages/plg_system_mokosuiteclient_backup/language/en-GB/plg_system_mokosuiteclient_backup.sys.ini new file mode 100644 index 00000000..07da83a8 --- /dev/null +++ b/source/packages/plg_system_mokosuiteclient_backup/language/en-GB/plg_system_mokosuiteclient_backup.sys.ini @@ -0,0 +1,3 @@ +; MokoSuiteClient Backup Bridge Plugin - System strings +PLG_SYSTEM_MOKOSUITECLIENT_BACKUP="System - MokoSuiteClient Backup" +PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC="MokoSuiteBackup detection and heartbeat integration." diff --git a/source/packages/plg_system_mokosuiteclient_backup/mokosuiteclient_backup.xml b/source/packages/plg_system_mokosuiteclient_backup/mokosuiteclient_backup.xml new file mode 100644 index 00000000..c0f05aeb --- /dev/null +++ b/source/packages/plg_system_mokosuiteclient_backup/mokosuiteclient_backup.xml @@ -0,0 +1,48 @@ + + + System - MokoSuiteClient Backup + mokosuiteclient_backup + Moko Consulting + 2026-06-18 + Copyright (C) 2026 Moko Consulting. All rights reserved. + GPL-3.0-or-later + hello@mokoconsulting.tech + https://mokoconsulting.tech + 02.34.84-dev + PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC + Moko\Plugin\System\MokoSuiteClientBackup + + + src + services + language + + + + en-GB/plg_system_mokosuiteclient_backup.ini + en-GB/plg_system_mokosuiteclient_backup.sys.ini + + + + +
+ + + + + + + + +
+
+
+
diff --git a/source/packages/plg_system_mokosuiteclient_backup/services/provider.php b/source/packages/plg_system_mokosuiteclient_backup/services/provider.php new file mode 100644 index 00000000..4ea63861 --- /dev/null +++ b/source/packages/plg_system_mokosuiteclient_backup/services/provider.php @@ -0,0 +1,34 @@ +set( + PluginInterface::class, + function (Container $container) { + $dispatcher = $container->get(DispatcherInterface::class); + $plugin = new Backup($dispatcher, (array) PluginHelper::getPlugin('system', 'mokosuiteclient_backup')); + $plugin->setApplication(Factory::getApplication()); + + return $plugin; + } + ); + } +}; diff --git a/source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php b/source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php new file mode 100644 index 00000000..6eee0741 --- /dev/null +++ b/source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php @@ -0,0 +1,219 @@ + 'onCollectHeartbeat', + ]; + } + + /** + * Collect backup status data for the heartbeat payload. + * + * Triggered by the monitor plugin before sending a heartbeat. + * Appends a 'backup' key to the heartbeat data array. + */ + public function onCollectHeartbeat($event): void + { + if (!$this->params->get('heartbeat_enabled', 1)) + { + return; + } + + try + { + $data = $this->getBackupStatus(); + $event->addResult('backup', $data); + } + catch (\Throwable $e) + { + Log::add('Backup bridge: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient'); + } + } + + /** + * Check if MokoSuiteBackup is installed. + * + * Queries the extensions table for the component, which is more + * reliable than checking for database tables alone. + */ + public function isBackupInstalled(): bool + { + try + { + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $query = $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__extensions')) + ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuitebackup')) + ->where($db->quoteName('type') . ' = ' . $db->quote('component')); + + $db->setQuery($query); + + return (int) $db->loadResult() > 0; + } + catch (\Throwable $e) + { + return false; + } + } + + /** + * Get backup status summary from MokoSuiteBackup. + * + * @return array Backup status data for heartbeat inclusion. + */ + public function getBackupStatus(): array + { + if (!$this->isBackupInstalled()) + { + return [ + 'installed' => false, + ]; + } + + $db = Factory::getContainer()->get(DatabaseInterface::class); + $tables = $db->getTableList(); + $prefix = $db->getPrefix(); + $statsTable = $prefix . 'mokosuitebackup_records'; + + if (!in_array($statsTable, $tables, true)) + { + return [ + 'installed' => true, + 'status' => 'degraded', + 'message' => 'Backup tables not found', + ]; + } + + // TODO: Query MokoSuiteBackup records table for latest backup status. + // + // This is a placeholder — the actual column names and table structure + // depend on MokoSuiteBackup's schema. Once that component is available + // locally, update this query to match its database layout. + // + // Expected return shape: + // [ + // 'installed' => true, + // 'status' => 'ok' | 'degraded', + // 'last_backup' => '2026-06-18 10:30:45', + // 'last_status' => 'complete' | 'failed' | 'partial', + // 'last_size_mb' => 512, + // 'days_since' => 2, + // 'total_backups'=> 42, + // 'recent_7d' => 5, + // 'destination' => 'local' | 's3' | 'remote', + // 'description' => 'Full site backup', + // ] + + return $this->queryBackupRecords($db); + } + + /** + * Query MokoSuiteBackup records for the latest backup summary. + * + * @param DatabaseInterface $db Database driver. + * + * @return array Backup status array. + */ + private function queryBackupRecords(DatabaseInterface $db): array + { + $staleDays = (int) $this->params->get('stale_days', 7); + + // Get the most recent backup record + $query = $db->getQuery(true) + ->select('*') + ->from($db->quoteName('#__mokosuitebackup_records')) + ->order($db->quoteName('id') . ' DESC'); + + $db->setQuery($query, 0, 1); + $latest = $db->loadObject(); + + if (!$latest) + { + return [ + 'installed' => true, + 'status' => 'degraded', + 'message' => 'No backups found', + ]; + } + + // Count total and recent backups + $db->setQuery( + $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__mokosuitebackup_records')) + ); + $totalBackups = (int) $db->loadResult(); + + $db->setQuery( + $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__mokosuitebackup_records')) + ->where($db->quoteName('created') . ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)') + ); + $recentBackups = (int) $db->loadResult(); + + // Determine status + $lastDate = $latest->created ?? ''; + $daysSince = $lastDate ? (int) ((time() - strtotime($lastDate)) / 86400) : 999; + $lastStatus = $latest->status ?? 'unknown'; + + $status = 'ok'; + + if ($lastStatus !== 'complete') + { + $status = 'degraded'; + } + elseif ($daysSince > $staleDays) + { + $status = 'degraded'; + } + + $sizeMb = !empty($latest->total_size) + ? round($latest->total_size / 1048576) + : null; + + return [ + 'installed' => true, + 'status' => $status, + 'last_backup' => $lastDate, + 'last_status' => $lastStatus, + 'last_size_mb' => $sizeMb, + 'days_since' => $daysSince, + 'total_backups' => $totalBackups, + 'recent_7d' => $recentBackups, + 'destination' => $latest->destination ?? null, + 'description' => $latest->description ?? null, + ]; + } +} diff --git a/source/pkg_mokosuiteclient.xml b/source/pkg_mokosuiteclient.xml index f79089f3..613d4d30 100644 --- a/source/pkg_mokosuiteclient.xml +++ b/source/pkg_mokosuiteclient.xml @@ -25,6 +25,7 @@ mod_mokosuiteclient_menu.zip mod_mokosuiteclient_cache.zip mod_mokosuiteclient_categories.zip + plg_system_mokosuiteclient_backup.zip plg_webservices_mokosuiteclient.zip plg_task_mokosuiteclientdemo.zip plg_task_mokosuiteclientsync.zip