5 Commits

Author SHA1 Message Date
gitea-actions[bot] affe2db36a chore(version): pre-release bump to 01.23.01-dev [skip ci] 2026-06-18 19:02:31 +00:00
jmiller 0c4ba5756b Merge pull request 'feat: add BackupStatusHelper for external plugin integration (#47)' (#48) from feature/47-backup-status-helper into dev
Generic: Project CI / Tests (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 6s
Generic: Project CI / Lint & Validate (push) Successful in 8s
2026-06-18 19:02:25 +00:00
Jonathan Miller 896c4c597e ci: retrigger after runner restart
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Universal: Auto Version Bump / Version Bump (push) Successful in 3s
Generic: Project CI / Lint & Validate (pull_request) Successful in 23s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
2026-06-18 12:46:53 -05:00
Jonathan Miller a77458a24a fix(status): use staleDays param for cutoff, remove unused profile_id (#47)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Universal: Auto Version Bump / Version Bump (push) Successful in 5s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 26s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
- Use $staleDays instead of hardcoded 7-day cutoff for recent/failure
  counts so the parameter is consistently applied
- Remove unused profile_id from SELECT (never included in return array)
2026-06-18 11:13:12 -05:00
Jonathan Miller 937e38bcc6 feat(utility): add BackupStatusHelper for external plugin integration (#47)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 3s
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 22s
Universal: PR Check / Validate PR (pull_request) Failing after 22s
Public API for MokoSuiteClient bridge plugin to query backup status
without depending on internal model structure. Provides getStatus()
and isInstalled() static methods.
2026-06-18 10:40:56 -05:00
18 changed files with 310 additions and 16 deletions
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<mokoplatform xmlns="https://standards.mokoconsulting.tech/mokoplatform/1.0" schema-version="1.0">
<identity>
<name>MokoSuiteBackup</name>
<display-name>Package - MokoSuiteBackup</display-name>
<org>MokoConsulting</org>
<description>Full-site backup and restore for Joomla — database, files, and configuration</description>
<version>01.23.01-dev</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
<platform>joomla</platform>
<standards-version>05.00.00</standards-version>
<standards-source>https://git.mokoconsulting.tech/MokoConsulting/mokoplatform</standards-source>
</governance>
<build>
<language>PHP</language>
<package-type>joomla-extension</package-type>
<entry-point>source/</entry-point>
</build>
</mokoplatform>
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokoplatform.Automation
# VERSION: 01.25.00
# VERSION: 01.23.01
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+4 -4
View File
@@ -1,10 +1,6 @@
# Changelog
## [Unreleased]
## [01.25.00] --- 2026-06-19
## [01.24.00] --- 2026-06-19
## [01.23.00] --- 2026-06-18
## [01.21.00] --- 2026-06-16
@@ -17,3 +13,7 @@
- Use `margin-inline-end` instead of `margin-right` for RTL layout support
## [01.08.00] --- 2026-06-07
## [01.07.00] --- 2026-06-07
## [01.06.00] --- 2026-06-07
+1 -1
View File
@@ -1,6 +1,6 @@
# MokoSuiteBackup
<!-- VERSION: 01.25.00 -->
<!-- VERSION: 01.23.01 -->
Full-site backup and restore for Joomla — database, files, and configuration.
+1 -1
View File
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuiteBackup</name>
<version>01.25.00</version>
<version>01.23.01-dev</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="component" method="upgrade">
<name>MokoSuiteBackup</name>
<version>01.25.00</version>
<version>01.23.01-dev</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -0,0 +1,173 @@
<?php
/**
* @package MokoSuiteBackup
* @subpackage com_mokosuitebackup
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Joomla\Component\MokoSuiteBackup\Administrator\Utility;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
/**
* Provides a public API for external plugins (e.g. MokoSuiteClient bridge)
* to query backup status without depending on internal model internals.
*
* @since 01.22.07
*/
class BackupStatusHelper
{
/**
* Get a summary of the latest backup status.
*
* Returns an array suitable for inclusion in heartbeat payloads.
*
* @param int $staleDays Days without backup before status is degraded.
*
* @return array
*/
public static function getStatus(int $staleDays = 7): array
{
try
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
}
catch (\Throwable $e)
{
return ['installed' => true, 'status' => 'error', 'message' => 'Database unavailable'];
}
// Most recently inserted backup record (by ID, any status)
$query = $db->getQuery(true)
->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');
$db->setQuery($query, 0, 1);
$latest = $db->loadObject();
if (!$latest)
{
return [
'installed' => true,
'status' => 'degraded',
'message' => 'No backups found',
];
}
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokosuitebackup_records'))
->where($db->quoteName('status') . ' = ' . $db->quote('complete'))
);
$totalBackups = (int) $db->loadResult();
$cutoff = date('Y-m-d H:i:s', strtotime("-{$staleDays} days"));
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokosuitebackup_records'))
->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 overall status
$daysSince = 999;
if (!empty($latest->backupstart) && $latest->backupstart !== '0000-00-00 00:00:00')
{
$daysSince = (int) ((time() - strtotime($latest->backupstart)) / 86400);
}
$status = 'ok';
if ($latest->status === 'fail')
{
$status = 'degraded';
}
elseif ($latest->status !== 'complete')
{
$status = ($latest->status === 'running') ? 'ok' : 'degraded';
}
elseif ($daysSince > $staleDays)
{
$status = 'degraded';
}
$sizeMb = $latest->total_size
? round($latest->total_size / 1048576)
: null;
return [
'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,
];
}
/**
* Check if MokoSuiteBackup component is installed.
*
* Useful for external plugins that want to check before calling getStatus().
*
* @return bool
*/
public static function isInstalled(): 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;
}
}
}
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="actionlog" method="upgrade">
<name>Action Log - MokoSuiteBackup</name>
<version>01.25.00</version>
<version>01.23.01-dev</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="console" method="upgrade">
<name>Console - MokoSuiteBackup</name>
<version>01.25.00</version>
<version>01.23.01-dev</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="content" method="upgrade">
<name>Content - MokoSuiteBackup</name>
<version>01.25.00</version>
<version>01.23.01-dev</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="quickicon" method="upgrade">
<name>Quick Icon - MokoSuiteBackup</name>
<version>01.25.00</version>
<version>01.23.01-dev</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteBackup</name>
<version>01.25.00</version>
<version>01.23.01-dev</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="task" method="upgrade">
<name>Task - MokoSuiteBackup</name>
<version>01.25.00</version>
<version>01.23.01-dev</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuiteBackup</name>
<version>01.25.00</version>
<version>01.23.01-dev</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
+1 -1
View File
@@ -8,7 +8,7 @@
<extension type="package" method="upgrade">
<name>Package - MokoSuiteBackup</name>
<packagename>mokosuitebackup</packagename>
<version>01.25.00</version>
<version>01.23.01-dev</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -0,0 +1,98 @@
<?php
/**
* @package MokoSuiteBackup
* @subpackage plg_webservices_mokosuitebackup
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*
* REST API endpoints — wire-compatible with the mcp_mokosuitebackup MCP server.
*
* Akeeba-compatible routes:
* POST /api/index.php/v1/mokosuitebackup/backup — Start backup
* GET /api/index.php/v1/mokosuitebackup/backups — List records
* DELETE /api/index.php/v1/mokosuitebackup/backup/:id — Delete record
* GET /api/index.php/v1/mokosuitebackup/backup/:id/download — Download archive
* GET /api/index.php/v1/mokosuitebackup/profiles — List profiles
*/
namespace Joomla\Plugin\WebServices\MokoSuiteBackup\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Router\ApiRouter;
use Joomla\Event\Event;
use Joomla\Event\SubscriberInterface;
use Joomla\Router\Route;
final class MokoSuiteBackupWebServices extends CMSPlugin implements SubscriberInterface
{
protected $autoloadLanguage = true;
public static function getSubscribedEvents(): array
{
return [
'onBeforeApiRoute' => 'onBeforeApiRoute',
];
}
public function onBeforeApiRoute(Event $event): void
{
/** @var ApiRouter $router */
[$router] = array_values($event->getArguments());
$defaults = [
'component' => 'com_mokosuitebackup',
'public' => false,
];
// Standard CRUD for backup records
$router->createCRUDRoutes('v1/mokosuitebackup/backups', 'backups', $defaults);
// Start a backup (POST)
$router->addRoute(
new Route(
['POST'],
'v1/mokosuitebackup/backup',
'backups.backup',
[],
$defaults
)
);
// Delete a backup (DELETE)
$router->addRoute(
new Route(
['DELETE'],
'v1/mokosuitebackup/backup/:id',
'backups.delete',
['id' => '(\d+)'],
$defaults
)
);
// Download a backup archive (GET)
$router->addRoute(
new Route(
['GET'],
'v1/mokosuitebackup/backup/:id/download',
'backups.download',
['id' => '(\d+)'],
$defaults
)
);
// List backup profiles (GET)
$router->addRoute(
new Route(
['GET'],
'v1/mokosuitebackup/profiles',
'backups.profiles',
[],
$defaults
)
);
}
}
+1
View File
@@ -0,0 +1 @@
<!DOCTYPE html><title></title>
+1
View File
@@ -0,0 +1 @@
<!DOCTYPE html><title></title>