From 559db324cb1f00694677a3c2556ac865080cfdfe Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 18 Jun 2026 10:24:54 -0500 Subject: [PATCH 1/5] 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 -- 2.52.0 From 0d731eafd0d4c80c1e86156fd9b7c0912ad93def Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 18 Jun 2026 10:37:22 -0500 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20swap=20demo/sync=20plugin=20manifest?= =?UTF-8?q?s=20=E2=80=94=20contents=20were=20in=20wrong=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The merge rename accidentally swapped the XML manifest contents between plg_task_mokosuiteclientdemo and plg_task_mokosuiteclientsync. The demo manifest contained the sync element/name/namespace and vice versa, causing JInstaller to look for mokosuiteclientsync.xml inside the demo plugin zip. --- .../mokosuiteclientdemo.xml | 14 +++++++------- .../mokosuiteclientsync.xml | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/source/packages/plg_task_mokosuiteclientdemo/mokosuiteclientdemo.xml b/source/packages/plg_task_mokosuiteclientdemo/mokosuiteclientdemo.xml index 153b22ca..fb33141a 100644 --- a/source/packages/plg_task_mokosuiteclientdemo/mokosuiteclientdemo.xml +++ b/source/packages/plg_task_mokosuiteclientdemo/mokosuiteclientdemo.xml @@ -4,8 +4,8 @@ SPDX-License-Identifier: GPL-3.0-or-later --> - Task - MokoSuiteClient Content Sync - mokosuiteclientsync + Task - MokoSuiteClient Demo Reset + mokosuiteclientdemo Moko Consulting 2026-05-30 Copyright (C) 2026 Moko Consulting. All rights reserved. @@ -13,11 +13,11 @@ hello@mokoconsulting.tech https://mokoconsulting.tech 02.34.84-dev - PLG_TASK_MOKOSUITESYNC_DESC - Moko\Plugin\Task\MokoSuiteClientSync + PLG_TASK_MOKOSUITEDEMO_DESC + Moko\Plugin\Task\MokoSuiteClientDemo - mokosuiteclientsync.xml + mokosuiteclientdemo.xml src services forms @@ -25,7 +25,7 @@ - en-GB/plg_task_mokosuiteclientsync.ini - en-GB/plg_task_mokosuiteclientsync.sys.ini + en-GB/plg_task_mokosuiteclientdemo.ini + en-GB/plg_task_mokosuiteclientdemo.sys.ini diff --git a/source/packages/plg_task_mokosuiteclientsync/mokosuiteclientsync.xml b/source/packages/plg_task_mokosuiteclientsync/mokosuiteclientsync.xml index fb33141a..153b22ca 100644 --- a/source/packages/plg_task_mokosuiteclientsync/mokosuiteclientsync.xml +++ b/source/packages/plg_task_mokosuiteclientsync/mokosuiteclientsync.xml @@ -4,8 +4,8 @@ SPDX-License-Identifier: GPL-3.0-or-later --> - Task - MokoSuiteClient Demo Reset - mokosuiteclientdemo + Task - MokoSuiteClient Content Sync + mokosuiteclientsync Moko Consulting 2026-05-30 Copyright (C) 2026 Moko Consulting. All rights reserved. @@ -13,11 +13,11 @@ hello@mokoconsulting.tech https://mokoconsulting.tech 02.34.84-dev - PLG_TASK_MOKOSUITEDEMO_DESC - Moko\Plugin\Task\MokoSuiteClientDemo + PLG_TASK_MOKOSUITESYNC_DESC + Moko\Plugin\Task\MokoSuiteClientSync - mokosuiteclientdemo.xml + mokosuiteclientsync.xml src services forms @@ -25,7 +25,7 @@ - en-GB/plg_task_mokosuiteclientdemo.ini - en-GB/plg_task_mokosuiteclientdemo.sys.ini + en-GB/plg_task_mokosuiteclientsync.ini + en-GB/plg_task_mokosuiteclientsync.sys.ini -- 2.52.0 From bca879f0d38adb99bd12238c39377110a6889ffa Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 18 Jun 2026 10:41:54 -0500 Subject: [PATCH 3/5] feat(backup): use real MokoSuiteBackup schema and prefer BackupStatusHelper (#208) Update bridge to use correct column names (backupstart/backupend, status values: complete/fail/running/pending). Prefer the BackupStatusHelper API when available, with direct table query fallback for older MokoSuiteBackup versions. --- .../src/Extension/Backup.php | 124 +++++++++++------- 1 file changed, 80 insertions(+), 44 deletions(-) diff --git a/source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php b/source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php index 6eee0741..b921da44 100644 --- a/source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php +++ b/source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php @@ -22,6 +22,10 @@ use Joomla\Event\SubscriberInterface; * Detects whether MokoSuiteBackup is installed and collects backup * status data for inclusion in heartbeat payloads to MokoSuiteHQ. * + * Prefers MokoSuiteBackup's own BackupStatusHelper when available, + * falling back to a direct table query if the helper class is missing + * (e.g. older versions of MokoSuiteBackup). + * * @since 02.34.84 */ class Backup extends CMSPlugin implements SubscriberInterface @@ -90,6 +94,9 @@ class Backup extends CMSPlugin implements SubscriberInterface /** * Get backup status summary from MokoSuiteBackup. * + * Prefers the BackupStatusHelper API when available. Falls back + * to a direct database query for compatibility with older versions. + * * @return array Backup status data for heartbeat inclusion. */ public function getBackupStatus(): array @@ -101,12 +108,22 @@ class Backup extends CMSPlugin implements SubscriberInterface ]; } - $db = Factory::getContainer()->get(DatabaseInterface::class); - $tables = $db->getTableList(); - $prefix = $db->getPrefix(); - $statsTable = $prefix . 'mokosuitebackup_records'; + // Prefer MokoSuiteBackup's own helper (clean public API) + $helperClass = 'Joomla\\Component\\MokoSuiteBackup\\Administrator\\Utility\\BackupStatusHelper'; - if (!in_array($statsTable, $tables, true)) + if (class_exists($helperClass)) + { + $staleDays = (int) $this->params->get('stale_days', 7); + + return $helperClass::getStatus($staleDays); + } + + // Fallback: direct table query for older MokoSuiteBackup versions + $db = Factory::getContainer()->get(DatabaseInterface::class); + $tables = $db->getTableList(); + $prefix = $db->getPrefix(); + + if (!in_array($prefix . 'mokosuitebackup_records', $tables, true)) { return [ 'installed' => true, @@ -115,32 +132,17 @@ class Backup extends CMSPlugin implements SubscriberInterface ]; } - // 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. * + * Column names match the MokoSuiteBackup schema: + * - backupstart/backupend (not created/modified) + * - status: pending, running, complete, fail + * - total_size in bytes + * * @param DatabaseInterface $db Database driver. * * @return array Backup status array. @@ -149,9 +151,19 @@ class Backup extends CMSPlugin implements SubscriberInterface { $staleDays = (int) $this->params->get('stale_days', 7); - // Get the most recent backup record + // Most recent backup record $query = $db->getQuery(true) - ->select('*') + ->select([ + $db->quoteName('id'), + $db->quoteName('description'), + $db->quoteName('status'), + $db->quoteName('backup_type'), + $db->quoteName('total_size'), + $db->quoteName('backupstart'), + $db->quoteName('backupend'), + $db->quoteName('origin'), + $db->quoteName('filesexist'), + ]) ->from($db->quoteName('#__mokosuitebackup_records')) ->order($db->quoteName('id') . ' DESC'); @@ -167,53 +179,77 @@ class Backup extends CMSPlugin implements SubscriberInterface ]; } - // Count total and recent backups + // Count completed backups $db->setQuery( $db->getQuery(true) ->select('COUNT(*)') ->from($db->quoteName('#__mokosuitebackup_records')) + ->where($db->quoteName('status') . ' = ' . $db->quote('complete')) ); $totalBackups = (int) $db->loadResult(); + // Recent completed backups (last 7 days) + $cutoff = date('Y-m-d H:i:s', strtotime('-7 days')); $db->setQuery( $db->getQuery(true) ->select('COUNT(*)') ->from($db->quoteName('#__mokosuitebackup_records')) - ->where($db->quoteName('created') . ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)') + ->where($db->quoteName('status') . ' = ' . $db->quote('complete')) + ->where($db->quoteName('backupstart') . ' >= ' . $db->quote($cutoff)) ); $recentBackups = (int) $db->loadResult(); + // Failures in last 7 days + $db->setQuery( + $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__mokosuitebackup_records')) + ->where($db->quoteName('status') . ' = ' . $db->quote('fail')) + ->where($db->quoteName('backupstart') . ' >= ' . $db->quote($cutoff)) + ); + $failCount7d = (int) $db->loadResult(); + // Determine status - $lastDate = $latest->created ?? ''; - $daysSince = $lastDate ? (int) ((time() - strtotime($lastDate)) / 86400) : 999; - $lastStatus = $latest->status ?? 'unknown'; + $daysSince = 999; + + if (!empty($latest->backupstart) && $latest->backupstart !== '0000-00-00 00:00:00') + { + $daysSince = (int) ((time() - strtotime($latest->backupstart)) / 86400); + } $status = 'ok'; - if ($lastStatus !== 'complete') + if ($latest->status === 'fail') { $status = 'degraded'; } + elseif ($latest->status !== 'complete') + { + $status = ($latest->status === 'running') ? 'ok' : 'degraded'; + } elseif ($daysSince > $staleDays) { $status = 'degraded'; } - $sizeMb = !empty($latest->total_size) + $sizeMb = $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, + 'installed' => true, + 'status' => $status, + 'last_backup' => $latest->backupstart, + 'last_status' => $latest->status, + 'last_size_mb' => $sizeMb, + 'days_since' => $daysSince, + 'backup_type' => $latest->backup_type, + 'origin' => $latest->origin, + 'total_backups' => $totalBackups, + 'recent_7d' => $recentBackups, + 'fail_count_7d' => $failCount7d, + 'files_exist' => (bool) $latest->filesexist, + 'description' => $latest->description, ]; } } -- 2.52.0 From 9b347dd13674fe7efda996c9482a370805f8ccd0 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 18 Jun 2026 11:13:29 -0500 Subject: [PATCH 4/5] fix(backup): send error on failure, use staleDays for cutoff, consistent shape (#208) - Send explicit error status when backup data collection fails instead of omitting the key (so HQ can distinguish failure from not-installed) - Add status='ok' to not-installed return for consistent array shape - Use staleDays param instead of hardcoded 7-day cutoff in fallback query --- .../src/Extension/Backup.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php b/source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php index b921da44..446b36c8 100644 --- a/source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php +++ b/source/packages/plg_system_mokosuiteclient_backup/src/Extension/Backup.php @@ -60,6 +60,14 @@ class Backup extends CMSPlugin implements SubscriberInterface catch (\Throwable $e) { Log::add('Backup bridge: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient'); + + // Send explicit error so HQ knows collection failed, + // rather than interpreting absence as "not installed" + $event->addResult('backup', [ + 'installed' => true, + 'status' => 'error', + 'message' => 'Failed to collect backup status', + ]); } } @@ -105,6 +113,7 @@ class Backup extends CMSPlugin implements SubscriberInterface { return [ 'installed' => false, + 'status' => 'ok', ]; } @@ -188,8 +197,7 @@ class Backup extends CMSPlugin implements SubscriberInterface ); $totalBackups = (int) $db->loadResult(); - // Recent completed backups (last 7 days) - $cutoff = date('Y-m-d H:i:s', strtotime('-7 days')); + $cutoff = date('Y-m-d H:i:s', strtotime("-{$staleDays} days")); $db->setQuery( $db->getQuery(true) ->select('COUNT(*)') -- 2.52.0 From 446539844dda98a0f5bc482b1ab19e4d7070429f Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 18 Jun 2026 12:55:05 -0500 Subject: [PATCH 5/5] ci: retrigger after runner restart -- 2.52.0