Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab163dfe06 | |||
| 6b1352d82c | |||
| 446539844d | |||
| 9b347dd136 | |||
| bca879f0d3 | |||
| 0d731eafd0 | |||
| 559db324cb |
@@ -1,243 +1 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokoplatform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||
# VERSION: 05.01.00
|
||||
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
|
||||
|
||||
name: "Universal: Pre-Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- dev
|
||||
pull_request_target:
|
||||
types: [synchronize, opened, reopened]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Pre-release channel'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- release-candidate
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev') ||
|
||||
(github.event_name == 'pull_request_target' && github.event.pull_request.base.ref == 'main')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }}
|
||||
|
||||
- name: Setup mokoplatform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
# Use pre-installed /opt/mokoplatform if available (updated by cron every 6h)
|
||||
if [ -f /opt/mokoplatform/cli/version_bump.php ] && [ -f /opt/mokoplatform/cli/manifest_element.php ] && [ -f /opt/mokoplatform/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/mokoplatform
|
||||
echo MOKO_CLI=/opt/mokoplatform/cli >> $GITHUB_ENV
|
||||
else
|
||||
echo Falling back to fresh clone
|
||||
if ! command -v composer > /dev/null 2>&1; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
|
||||
fi
|
||||
rm -rf /tmp/mokoplatform-api
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokoplatform.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokoplatform-api
|
||||
cd /tmp/mokoplatform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/mokoplatform-api/cli >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: |
|
||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve metadata and bump version
|
||||
id: meta
|
||||
run: |
|
||||
# Auto-detect stability: RC for PRs targeting main, else use input or default to development
|
||||
if [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.pull_request.base.ref }}" = "main" ]; then
|
||||
STABILITY="release-candidate"
|
||||
else
|
||||
STABILITY="${{ inputs.stability || 'development' }}"
|
||||
fi
|
||||
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
esac
|
||||
|
||||
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
|
||||
case "$STABILITY" in
|
||||
release-candidate) BUMP="minor" ;;
|
||||
*) BUMP="patch" ;;
|
||||
esac
|
||||
|
||||
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
|
||||
|
||||
# Set stability suffix and verify consistency
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
|
||||
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Append suffix for output
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
VERSION="${VERSION}${SUFFIX}"
|
||||
fi
|
||||
|
||||
# Commit version bump
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1
|
||||
}
|
||||
|
||||
# Auto-detect element via manifest_element.php
|
||||
php ${MOKO_CLI}/manifest_element.php \
|
||||
--path . --version "$VERSION" --stability "$STABILITY" \
|
||||
--repo "${GITEA_REPO}" --github-output
|
||||
|
||||
# Read back element outputs
|
||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
||||
|
||||
- name: Create release
|
||||
id: release
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch dev --prerelease
|
||||
|
||||
- name: Update release notes from CHANGELOG.md
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||
else
|
||||
NOTES="Release ${VERSION}"
|
||||
fi
|
||||
|
||||
# Update release body via API
|
||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$RELEASE_ID" ]; then
|
||||
python3 -c "
|
||||
import json, urllib.request
|
||||
body = open('/dev/stdin').read()
|
||||
payload = json.dumps({'body': body}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/releases/${RELEASE_ID}',
|
||||
data=payload, method='PATCH',
|
||||
headers={
|
||||
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
urllib.request.urlopen(req)
|
||||
" <<< "$NOTES"
|
||||
echo "Release notes updated from CHANGELOG.md"
|
||||
fi
|
||||
|
||||
- name: Build package and upload
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
# updates.xml is generated dynamically by MokoGitea license server
|
||||
# No need to build, commit, or sync updates.xml from workflows
|
||||
|
||||
- name: "Delete lesser pre-release channels (cascade)"
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
php ${MOKO_CLI}/release_cascade.php \
|
||||
--stability "${{ steps.meta.outputs.stability }}" \
|
||||
--token "${TOKEN}" \
|
||||
--api-base "${API_BASE}"
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
placeholder
|
||||
+13
@@ -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."
|
||||
+3
@@ -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."
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="plugin" group="system" method="upgrade">
|
||||
<name>System - MokoSuiteClient Backup</name>
|
||||
<element>mokosuiteclient_backup</element>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-06-18</creationDate>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.34.84-dev</version>
|
||||
<description>PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\System\MokoSuiteClientBackup</namespace>
|
||||
|
||||
<files>
|
||||
<folder>src</folder>
|
||||
<folder>services</folder>
|
||||
<folder>language</folder>
|
||||
</files>
|
||||
|
||||
<languages folder="language">
|
||||
<language tag="en-GB">en-GB/plg_system_mokosuiteclient_backup.ini</language>
|
||||
<language tag="en-GB">en-GB/plg_system_mokosuiteclient_backup.sys.ini</language>
|
||||
</languages>
|
||||
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic"
|
||||
label="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC"
|
||||
description="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC_DESC">
|
||||
|
||||
<field name="heartbeat_enabled" type="radio" default="1"
|
||||
label="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_LABEL"
|
||||
description="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_DESC"
|
||||
class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
|
||||
<field name="stale_days" type="number" default="7"
|
||||
label="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_LABEL"
|
||||
description="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_DESC"
|
||||
min="1" max="90" step="1" />
|
||||
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteClient
|
||||
* @subpackage plg_system_mokosuiteclient_backup
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Moko\Plugin\System\MokoSuiteClientBackup\Extension\Backup;
|
||||
|
||||
return new class implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->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;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteClient
|
||||
* @subpackage plg_system_mokosuiteclient_backup
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\System\MokoSuiteClientBackup\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
/**
|
||||
* MokoSuiteClient Backup Bridge Plugin
|
||||
*
|
||||
* 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
|
||||
{
|
||||
protected $autoloadLanguage = true;
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onMokoSuiteClientCollectHeartbeat' => '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');
|
||||
|
||||
// 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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
{
|
||||
if (!$this->isBackupInstalled())
|
||||
{
|
||||
return [
|
||||
'installed' => false,
|
||||
'status' => 'ok',
|
||||
];
|
||||
}
|
||||
|
||||
// Prefer MokoSuiteBackup's own helper (clean public API)
|
||||
$helperClass = 'Joomla\\Component\\MokoSuiteBackup\\Administrator\\Utility\\BackupStatusHelper';
|
||||
|
||||
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,
|
||||
'status' => 'degraded',
|
||||
'message' => 'Backup tables not found',
|
||||
];
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
private function queryBackupRecords(DatabaseInterface $db): array
|
||||
{
|
||||
$staleDays = (int) $this->params->get('stale_days', 7);
|
||||
|
||||
// Most recent backup record
|
||||
$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',
|
||||
];
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
$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 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
<extension type="plugin" group="task" method="upgrade">
|
||||
<name>Task - MokoSuiteClient Content Sync</name>
|
||||
<element>mokosuiteclientsync</element>
|
||||
<name>Task - MokoSuiteClient Demo Reset</name>
|
||||
<element>mokosuiteclientdemo</element>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-05-30</creationDate>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
@@ -13,11 +13,11 @@
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.34.84-dev</version>
|
||||
<description>PLG_TASK_MOKOSUITESYNC_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientSync</namespace>
|
||||
<description>PLG_TASK_MOKOSUITEDEMO_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientDemo</namespace>
|
||||
|
||||
<files>
|
||||
<filename plugin="mokosuiteclientsync">mokosuiteclientsync.xml</filename>
|
||||
<filename plugin="mokosuiteclientdemo">mokosuiteclientdemo.xml</filename>
|
||||
<folder>src</folder>
|
||||
<folder>services</folder>
|
||||
<folder>forms</folder>
|
||||
@@ -25,7 +25,7 @@
|
||||
</files>
|
||||
|
||||
<languages folder="language">
|
||||
<language tag="en-GB">en-GB/plg_task_mokosuiteclientsync.ini</language>
|
||||
<language tag="en-GB">en-GB/plg_task_mokosuiteclientsync.sys.ini</language>
|
||||
<language tag="en-GB">en-GB/plg_task_mokosuiteclientdemo.ini</language>
|
||||
<language tag="en-GB">en-GB/plg_task_mokosuiteclientdemo.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
<extension type="plugin" group="task" method="upgrade">
|
||||
<name>Task - MokoSuiteClient Demo Reset</name>
|
||||
<element>mokosuiteclientdemo</element>
|
||||
<name>Task - MokoSuiteClient Content Sync</name>
|
||||
<element>mokosuiteclientsync</element>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-05-30</creationDate>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
@@ -13,11 +13,11 @@
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.34.84-dev</version>
|
||||
<description>PLG_TASK_MOKOSUITEDEMO_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientDemo</namespace>
|
||||
<description>PLG_TASK_MOKOSUITESYNC_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientSync</namespace>
|
||||
|
||||
<files>
|
||||
<filename plugin="mokosuiteclientdemo">mokosuiteclientdemo.xml</filename>
|
||||
<filename plugin="mokosuiteclientsync">mokosuiteclientsync.xml</filename>
|
||||
<folder>src</folder>
|
||||
<folder>services</folder>
|
||||
<folder>forms</folder>
|
||||
@@ -25,7 +25,7 @@
|
||||
</files>
|
||||
|
||||
<languages folder="language">
|
||||
<language tag="en-GB">en-GB/plg_task_mokosuiteclientdemo.ini</language>
|
||||
<language tag="en-GB">en-GB/plg_task_mokosuiteclientdemo.sys.ini</language>
|
||||
<language tag="en-GB">en-GB/plg_task_mokosuiteclientsync.ini</language>
|
||||
<language tag="en-GB">en-GB/plg_task_mokosuiteclientsync.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<file type="module" id="mod_mokosuiteclient_menu" client="administrator">mod_mokosuiteclient_menu.zip</file>
|
||||
<file type="module" id="mod_mokosuiteclient_cache" client="administrator">mod_mokosuiteclient_cache.zip</file>
|
||||
<file type="module" id="mod_mokosuiteclient_categories" client="administrator">mod_mokosuiteclient_categories.zip</file>
|
||||
<file type="plugin" id="plg_system_mokosuiteclient_backup" group="system">plg_system_mokosuiteclient_backup.zip</file>
|
||||
<file type="plugin" id="plg_webservices_mokosuiteclient" group="webservices">plg_webservices_mokosuiteclient.zip</file>
|
||||
<file type="plugin" id="plg_task_mokosuiteclientdemo" group="task">plg_task_mokosuiteclientdemo.zip</file>
|
||||
<file type="plugin" id="plg_task_mokosuiteclientsync" group="task">plg_task_mokosuiteclientsync.zip</file>
|
||||
|
||||
Reference in New Issue
Block a user