feat: add scheduled tasks, individual fields, remote storage

- Add plg_task_mokobackup: Joomla Scheduled Tasks integration so each
  backup profile can run on its own schedule (like Akeeba Backup Pro)
- Replace JSON config/filters with individual form fields and DB columns
- Add FTP/FTPS and Google Drive remote storage options per profile
- Add archive settings tab (format, compression, split size, backup dir)
- Add exclusion filter fields (dirs, files, tables) as newline-separated
  textareas instead of raw JSON

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Miller
2026-06-02 13:58:50 -05:00
parent b82c1f8a24
commit 201a281e3a
26 changed files with 590 additions and 44 deletions
+6 -3
View File
@@ -3,7 +3,11 @@
## [Unreleased]
### Added
- Initial package structure with component, system plugin, and webservices plugin
- Initial package structure with component, system plugin, task plugin, and webservices plugin
- Joomla Scheduled Tasks integration (plg_task_mokobackup) — create multiple tasks, each running a different backup profile on its own schedule
- Individual form fields for all profile settings (no raw JSON)
- Remote storage support: FTP/FTPS and Google Drive offsite backup upload
- Per-profile archive settings: format, compression level, split size, backup directory
- Backup engine with step-based execution for large sites
- Database dumper with table-level granularity
- File scanner with directory exclusion filters
@@ -13,5 +17,4 @@
- Admin dashboard with backup history
- CLI script for cron/scheduled backups
- REST API compatible with MokoBackup MCP server
- System plugin for scheduled backup triggers
- Automatic old backup cleanup with configurable retention
- System plugin for automatic backup cleanup with configurable retention
+7 -2
View File
@@ -43,10 +43,15 @@ This is a Joomla **package** extension (`pkg_mokobackup`) containing three sub-e
- CLI: `cli/mokobackup.php` for cron-based backups
### plg_system_mokobackup (System Plugin)
- Handles scheduled backup triggers
- Cleanup of expired backup archives
- Cleanup of expired backup archives (age + count limits)
- Namespace: `Joomla\Plugin\System\MokoBackup`
### plg_task_mokobackup (Task Plugin)
- Integrates with Joomla's Scheduled Tasks (com_scheduler)
- Registers "Run Backup Profile" task type
- Each scheduled task selects a backup profile — create multiple tasks for different schedules
- Namespace: `Joomla\Plugin\Task\MokoBackup`
### plg_webservices_mokobackup (WebServices Plugin)
- REST API for remote backup management
- Wire-compatible with existing mcp_mokobackup MCP server
+189 -12
View File
@@ -27,14 +27,46 @@
<option value="database">COM_MOKOBACKUP_TYPE_DATABASE</option>
<option value="files">COM_MOKOBACKUP_TYPE_FILES</option>
</field>
</fieldset>
<fieldset name="archive" label="COM_MOKOBACKUP_FIELDSET_ARCHIVE">
<field
name="config"
type="textarea"
label="COM_MOKOBACKUP_FIELD_CONFIG"
description="COM_MOKOBACKUP_FIELD_CONFIG_DESC"
rows="8"
filter="raw"
hint='{"archive_format":"zip","compression_level":5,"split_size":0,"backup_dir":"administrator/components/com_mokobackup/backups"}'
name="archive_format"
type="list"
label="COM_MOKOBACKUP_FIELD_ARCHIVE_FORMAT"
description="COM_MOKOBACKUP_FIELD_ARCHIVE_FORMAT_DESC"
default="zip"
>
<option value="zip">ZIP</option>
</field>
<field
name="compression_level"
type="list"
label="COM_MOKOBACKUP_FIELD_COMPRESSION"
description="COM_MOKOBACKUP_FIELD_COMPRESSION_DESC"
default="5"
>
<option value="0">COM_MOKOBACKUP_COMPRESSION_NONE</option>
<option value="1">COM_MOKOBACKUP_COMPRESSION_FASTEST</option>
<option value="5">COM_MOKOBACKUP_COMPRESSION_NORMAL</option>
<option value="9">COM_MOKOBACKUP_COMPRESSION_BEST</option>
</field>
<field
name="split_size"
type="number"
label="COM_MOKOBACKUP_FIELD_SPLIT_SIZE"
description="COM_MOKOBACKUP_FIELD_SPLIT_SIZE_DESC"
default="0"
min="0"
hint="0 = no splitting"
/>
<field
name="backup_dir"
type="text"
label="COM_MOKOBACKUP_FIELD_BACKUP_DIR"
description="COM_MOKOBACKUP_FIELD_BACKUP_DIR_DESC"
default="administrator/components/com_mokobackup/backups"
maxlength="512"
/>
</fieldset>
@@ -62,13 +94,158 @@
<fieldset name="filters" label="COM_MOKOBACKUP_FIELDSET_FILTERS">
<field
name="filters"
name="exclude_dirs"
type="textarea"
label="COM_MOKOBACKUP_FIELD_FILTERS"
description="COM_MOKOBACKUP_FIELD_FILTERS_DESC"
rows="12"
label="COM_MOKOBACKUP_FIELD_EXCLUDE_DIRS"
description="COM_MOKOBACKUP_FIELD_EXCLUDE_DIRS_DESC"
rows="6"
filter="raw"
hint='{"exclude_dirs":["tmp","cache","logs"],"exclude_files":[],"exclude_tables":["#__session"]}'
hint="tmp&#10;cache&#10;logs&#10;administrator/logs"
/>
<field
name="exclude_files"
type="textarea"
label="COM_MOKOBACKUP_FIELD_EXCLUDE_FILES"
description="COM_MOKOBACKUP_FIELD_EXCLUDE_FILES_DESC"
rows="4"
filter="raw"
hint=".gitignore&#10;*.bak&#10;*.tmp"
/>
<field
name="exclude_tables"
type="textarea"
label="COM_MOKOBACKUP_FIELD_EXCLUDE_TABLES"
description="COM_MOKOBACKUP_FIELD_EXCLUDE_TABLES_DESC"
rows="4"
filter="raw"
hint="#__session&#10;#__mail_queue"
/>
</fieldset>
<fieldset name="remote" label="COM_MOKOBACKUP_FIELDSET_REMOTE">
<field
name="remote_storage"
type="list"
label="COM_MOKOBACKUP_FIELD_REMOTE_STORAGE"
description="COM_MOKOBACKUP_FIELD_REMOTE_STORAGE_DESC"
default="none"
>
<option value="none">COM_MOKOBACKUP_REMOTE_NONE</option>
<option value="ftp">COM_MOKOBACKUP_REMOTE_FTP</option>
<option value="google_drive">COM_MOKOBACKUP_REMOTE_GDRIVE</option>
</field>
<field
name="remote_keep_local"
type="radio"
label="COM_MOKOBACKUP_FIELD_KEEP_LOCAL"
description="COM_MOKOBACKUP_FIELD_KEEP_LOCAL_DESC"
default="1"
class="btn-group"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
<fieldset name="ftp" label="COM_MOKOBACKUP_FIELDSET_FTP">
<field
name="ftp_host"
type="text"
label="COM_MOKOBACKUP_FIELD_FTP_HOST"
description="COM_MOKOBACKUP_FIELD_FTP_HOST_DESC"
maxlength="255"
showon="remote_storage:ftp"
/>
<field
name="ftp_port"
type="number"
label="COM_MOKOBACKUP_FIELD_FTP_PORT"
description="COM_MOKOBACKUP_FIELD_FTP_PORT_DESC"
default="21"
min="1"
max="65535"
showon="remote_storage:ftp"
/>
<field
name="ftp_username"
type="text"
label="COM_MOKOBACKUP_FIELD_FTP_USERNAME"
maxlength="255"
showon="remote_storage:ftp"
/>
<field
name="ftp_password"
type="password"
label="COM_MOKOBACKUP_FIELD_FTP_PASSWORD"
maxlength="255"
showon="remote_storage:ftp"
/>
<field
name="ftp_path"
type="text"
label="COM_MOKOBACKUP_FIELD_FTP_PATH"
description="COM_MOKOBACKUP_FIELD_FTP_PATH_DESC"
default="/backups"
maxlength="512"
showon="remote_storage:ftp"
/>
<field
name="ftp_passive"
type="radio"
label="COM_MOKOBACKUP_FIELD_FTP_PASSIVE"
description="COM_MOKOBACKUP_FIELD_FTP_PASSIVE_DESC"
default="1"
class="btn-group"
showon="remote_storage:ftp"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="ftp_ssl"
type="radio"
label="COM_MOKOBACKUP_FIELD_FTP_SSL"
description="COM_MOKOBACKUP_FIELD_FTP_SSL_DESC"
default="0"
class="btn-group"
showon="remote_storage:ftp"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
<fieldset name="google_drive" label="COM_MOKOBACKUP_FIELDSET_GDRIVE">
<field
name="gdrive_client_id"
type="text"
label="COM_MOKOBACKUP_FIELD_GDRIVE_CLIENT_ID"
description="COM_MOKOBACKUP_FIELD_GDRIVE_CLIENT_ID_DESC"
maxlength="255"
showon="remote_storage:google_drive"
/>
<field
name="gdrive_client_secret"
type="password"
label="COM_MOKOBACKUP_FIELD_GDRIVE_CLIENT_SECRET"
maxlength="255"
showon="remote_storage:google_drive"
/>
<field
name="gdrive_refresh_token"
type="text"
label="COM_MOKOBACKUP_FIELD_GDRIVE_REFRESH_TOKEN"
description="COM_MOKOBACKUP_FIELD_GDRIVE_REFRESH_TOKEN_DESC"
maxlength="512"
showon="remote_storage:google_drive"
/>
<field
name="gdrive_folder_id"
type="text"
label="COM_MOKOBACKUP_FIELD_GDRIVE_FOLDER_ID"
description="COM_MOKOBACKUP_FIELD_GDRIVE_FOLDER_ID_DESC"
maxlength="255"
showon="remote_storage:google_drive"
/>
</fieldset>
</form>
@@ -44,17 +44,13 @@ COM_MOKOBACKUP_HEADING_SIZE_ASC="Size ascending"
COM_MOKOBACKUP_HEADING_TITLE_ASC="Title ascending"
COM_MOKOBACKUP_HEADING_TITLE_DESC="Title descending"
; Fields
; General fields
COM_MOKOBACKUP_FIELD_TITLE="Title"
COM_MOKOBACKUP_FIELD_TITLE_DESC="Profile name"
COM_MOKOBACKUP_FIELD_DESCRIPTION="Description"
COM_MOKOBACKUP_FIELD_DESCRIPTION_DESC="Brief description of this profile"
COM_MOKOBACKUP_FIELD_BACKUP_TYPE="Backup Type"
COM_MOKOBACKUP_FIELD_BACKUP_TYPE_DESC="What to include in the backup"
COM_MOKOBACKUP_FIELD_CONFIG="Configuration (JSON)"
COM_MOKOBACKUP_FIELD_CONFIG_DESC="JSON configuration for archive format, compression, and backup directory"
COM_MOKOBACKUP_FIELD_FILTERS="Filters (JSON)"
COM_MOKOBACKUP_FIELD_FILTERS_DESC="JSON filters for excluding directories, files, and database tables"
COM_MOKOBACKUP_FIELD_STATUS="Status"
COM_MOKOBACKUP_FIELD_ORIGIN="Origin"
COM_MOKOBACKUP_FIELD_SIZE="Total Size"
@@ -64,6 +60,60 @@ COM_MOKOBACKUP_FIELD_ARCHIVE="Archive Name"
COM_MOKOBACKUP_FIELD_FILES_COUNT="Files Count"
COM_MOKOBACKUP_FIELD_TABLES_COUNT="Tables Count"
; Archive settings
COM_MOKOBACKUP_FIELD_ARCHIVE_FORMAT="Archive Format"
COM_MOKOBACKUP_FIELD_ARCHIVE_FORMAT_DESC="Format for the backup archive file"
COM_MOKOBACKUP_FIELD_COMPRESSION="Compression Level"
COM_MOKOBACKUP_FIELD_COMPRESSION_DESC="Higher compression = smaller file but slower"
COM_MOKOBACKUP_COMPRESSION_NONE="None (fastest)"
COM_MOKOBACKUP_COMPRESSION_FASTEST="Low (fast)"
COM_MOKOBACKUP_COMPRESSION_NORMAL="Normal (balanced)"
COM_MOKOBACKUP_COMPRESSION_BEST="Maximum (smallest)"
COM_MOKOBACKUP_FIELD_SPLIT_SIZE="Split Size (MB)"
COM_MOKOBACKUP_FIELD_SPLIT_SIZE_DESC="Split archive into parts of this size in MB. 0 = no splitting."
COM_MOKOBACKUP_FIELD_BACKUP_DIR="Backup Directory"
COM_MOKOBACKUP_FIELD_BACKUP_DIR_DESC="Relative path from Joomla root where backup archives are stored"
; Exclusion filter fields
COM_MOKOBACKUP_FIELD_EXCLUDE_DIRS="Exclude Directories"
COM_MOKOBACKUP_FIELD_EXCLUDE_DIRS_DESC="One directory path per line (relative to Joomla root). These directories will be skipped during file backup."
COM_MOKOBACKUP_FIELD_EXCLUDE_FILES="Exclude Files"
COM_MOKOBACKUP_FIELD_EXCLUDE_FILES_DESC="One filename or pattern per line. Supports wildcards (e.g. *.bak, *.tmp)."
COM_MOKOBACKUP_FIELD_EXCLUDE_TABLES="Exclude Tables"
COM_MOKOBACKUP_FIELD_EXCLUDE_TABLES_DESC="One table name per line (use #__ prefix). These tables will be skipped during database dump."
; Remote storage fields
COM_MOKOBACKUP_FIELD_REMOTE_STORAGE="Remote Storage"
COM_MOKOBACKUP_FIELD_REMOTE_STORAGE_DESC="Optionally upload backup archives to a remote location after creation"
COM_MOKOBACKUP_REMOTE_NONE="None (local only)"
COM_MOKOBACKUP_REMOTE_FTP="FTP / FTPS"
COM_MOKOBACKUP_REMOTE_GDRIVE="Google Drive"
COM_MOKOBACKUP_FIELD_KEEP_LOCAL="Keep Local Copy"
COM_MOKOBACKUP_FIELD_KEEP_LOCAL_DESC="Keep the local backup file after uploading to remote storage"
; FTP fields
COM_MOKOBACKUP_FIELD_FTP_HOST="FTP Host"
COM_MOKOBACKUP_FIELD_FTP_HOST_DESC="FTP server hostname or IP address"
COM_MOKOBACKUP_FIELD_FTP_PORT="FTP Port"
COM_MOKOBACKUP_FIELD_FTP_PORT_DESC="FTP server port (default: 21)"
COM_MOKOBACKUP_FIELD_FTP_USERNAME="FTP Username"
COM_MOKOBACKUP_FIELD_FTP_PASSWORD="FTP Password"
COM_MOKOBACKUP_FIELD_FTP_PATH="Remote Path"
COM_MOKOBACKUP_FIELD_FTP_PATH_DESC="Directory on the FTP server to upload backups to"
COM_MOKOBACKUP_FIELD_FTP_PASSIVE="Passive Mode"
COM_MOKOBACKUP_FIELD_FTP_PASSIVE_DESC="Use passive mode for FTP connections (recommended)"
COM_MOKOBACKUP_FIELD_FTP_SSL="Use FTPS (SSL)"
COM_MOKOBACKUP_FIELD_FTP_SSL_DESC="Connect using FTPS (FTP over SSL/TLS)"
; Google Drive fields
COM_MOKOBACKUP_FIELD_GDRIVE_CLIENT_ID="Google Client ID"
COM_MOKOBACKUP_FIELD_GDRIVE_CLIENT_ID_DESC="OAuth 2.0 Client ID from Google Cloud Console"
COM_MOKOBACKUP_FIELD_GDRIVE_CLIENT_SECRET="Google Client Secret"
COM_MOKOBACKUP_FIELD_GDRIVE_REFRESH_TOKEN="Refresh Token"
COM_MOKOBACKUP_FIELD_GDRIVE_REFRESH_TOKEN_DESC="OAuth 2.0 refresh token for offline access"
COM_MOKOBACKUP_FIELD_GDRIVE_FOLDER_ID="Drive Folder ID"
COM_MOKOBACKUP_FIELD_GDRIVE_FOLDER_ID_DESC="Google Drive folder ID where backups will be uploaded. Find this in the folder URL."
; Backup types
COM_MOKOBACKUP_TYPE_FULL="Full Site (Database + Files)"
COM_MOKOBACKUP_TYPE_DATABASE="Database Only"
@@ -80,12 +130,18 @@ COM_MOKOBACKUP_FILTER_SEARCH="Search"
COM_MOKOBACKUP_FILTER_STATUS="Status"
COM_MOKOBACKUP_FILTER_STATUS_ALL="- Select Status -"
; Tabs
; Tabs and fieldsets
COM_MOKOBACKUP_TAB_GENERAL="General"
COM_MOKOBACKUP_TAB_ARCHIVE="Archive Settings"
COM_MOKOBACKUP_TAB_FILTERS="Exclusion Filters"
COM_MOKOBACKUP_TAB_REMOTE="Remote Storage"
COM_MOKOBACKUP_FIELDSET_GENERAL="General"
COM_MOKOBACKUP_FIELDSET_ARCHIVE="Archive Settings"
COM_MOKOBACKUP_FIELDSET_STATUS="Status"
COM_MOKOBACKUP_FIELDSET_FILTERS="Exclusion Filters"
COM_MOKOBACKUP_FIELDSET_REMOTE="Remote Storage"
COM_MOKOBACKUP_FIELDSET_FTP="FTP Settings"
COM_MOKOBACKUP_FIELDSET_GDRIVE="Google Drive Settings"
; Errors
COM_MOKOBACKUP_ERROR_FILE_NOT_FOUND="Backup archive file not found or has been deleted."
@@ -1,14 +1,32 @@
CREATE TABLE IF NOT EXISTS `#__mokobackup_profiles` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL DEFAULT '',
`description` TEXT NOT NULL,
`backup_type` VARCHAR(20) NOT NULL DEFAULT 'full' COMMENT 'full, database, files',
`config` MEDIUMTEXT NOT NULL COMMENT 'JSON: archive format, compression, paths',
`filters` MEDIUMTEXT NOT NULL COMMENT 'JSON: excluded dirs, files, tables',
`published` TINYINT(1) NOT NULL DEFAULT 1,
`ordering` INT(11) NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
`modified` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL DEFAULT '',
`description` TEXT NOT NULL,
`backup_type` VARCHAR(20) NOT NULL DEFAULT 'full' COMMENT 'full, database, files',
`archive_format` VARCHAR(10) NOT NULL DEFAULT 'zip',
`compression_level` TINYINT(1) UNSIGNED NOT NULL DEFAULT 5 COMMENT '0=none, 9=max',
`split_size` INT(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '0=no split, otherwise MB per part',
`backup_dir` VARCHAR(512) NOT NULL DEFAULT 'administrator/components/com_mokobackup/backups',
`exclude_dirs` TEXT NOT NULL COMMENT 'Newline-separated directory paths to exclude',
`exclude_files` TEXT NOT NULL COMMENT 'Newline-separated filename patterns to exclude',
`exclude_tables` TEXT NOT NULL COMMENT 'Newline-separated table names to exclude',
`remote_storage` VARCHAR(20) NOT NULL DEFAULT 'none' COMMENT 'none, ftp, google_drive',
`ftp_host` VARCHAR(255) NOT NULL DEFAULT '',
`ftp_port` INT(5) UNSIGNED NOT NULL DEFAULT 21,
`ftp_username` VARCHAR(255) NOT NULL DEFAULT '',
`ftp_password` VARCHAR(255) NOT NULL DEFAULT '',
`ftp_path` VARCHAR(512) NOT NULL DEFAULT '/backups',
`ftp_passive` TINYINT(1) NOT NULL DEFAULT 1,
`ftp_ssl` TINYINT(1) NOT NULL DEFAULT 0,
`gdrive_client_id` VARCHAR(255) NOT NULL DEFAULT '',
`gdrive_client_secret` VARCHAR(255) NOT NULL DEFAULT '',
`gdrive_refresh_token` VARCHAR(512) NOT NULL DEFAULT '',
`gdrive_folder_id` VARCHAR(255) NOT NULL DEFAULT '',
`remote_keep_local` TINYINT(1) NOT NULL DEFAULT 1 COMMENT 'Keep local copy after upload',
`published` TINYINT(1) NOT NULL DEFAULT 1,
`ordering` INT(11) NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
`modified` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
KEY `idx_published` (`published`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -40,8 +58,16 @@ CREATE TABLE IF NOT EXISTS `#__mokobackup_records` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Insert default backup profile
INSERT INTO `#__mokobackup_profiles` (`id`, `title`, `description`, `backup_type`, `config`, `filters`, `published`, `ordering`, `created`, `modified`)
VALUES (1, 'Default Backup Profile', 'Full site backup with default settings', 'full',
'{"archive_format":"zip","compression_level":5,"split_size":0,"backup_dir":"administrator/components/com_mokobackup/backups"}',
'{"exclude_dirs":["administrator/components/com_mokobackup/backups","tmp","cache","logs","administrator/logs"],"exclude_files":[".gitignore",".htaccess.bak"],"exclude_tables":["#__session"]}',
1, 1, NOW(), NOW());
INSERT INTO `#__mokobackup_profiles` (
`id`, `title`, `description`, `backup_type`,
`archive_format`, `compression_level`, `split_size`, `backup_dir`,
`exclude_dirs`, `exclude_files`, `exclude_tables`,
`published`, `ordering`, `created`, `modified`
) VALUES (
1, 'Default Backup Profile', 'Full site backup with default settings', 'full',
'zip', 5, 0, 'administrator/components/com_mokobackup/backups',
'administrator/components/com_mokobackup/backups\ntmp\ncache\nlogs\nadministrator/logs',
'.gitignore\n.htaccess.bak',
'#__session',
1, 1, NOW(), NOW()
);
@@ -44,11 +44,13 @@ class BackupEngine
return ['success' => false, 'message' => 'Profile not found: ' . $profileId];
}
$config = json_decode($profile->config ?: '{}', true) ?: [];
$filters = json_decode($profile->filters ?: '{}', true) ?: [];
// Read settings directly from profile columns
$excludeDirs = $this->parseNewlineList($profile->exclude_dirs ?? '');
$excludeFiles = $this->parseNewlineList($profile->exclude_files ?? '');
$excludeTables = $this->parseNewlineList($profile->exclude_tables ?? '');
// Determine backup directory
$this->backupDir = JPATH_ROOT . '/' . ($config['backup_dir'] ?? 'administrator/components/com_mokobackup/backups');
$this->backupDir = JPATH_ROOT . '/' . ($profile->backup_dir ?: 'administrator/components/com_mokobackup/backups');
if (!is_dir($this->backupDir)) {
mkdir($this->backupDir, 0755, true);
@@ -105,7 +107,7 @@ class BackupEngine
// Step 1: Database dump (unless files-only)
if ($profile->backup_type !== 'files') {
$this->log('Starting database dump...');
$dumper = new DatabaseDumper($filters['exclude_tables'] ?? []);
$dumper = new DatabaseDumper($excludeTables);
$sqlDump = $dumper->dump();
$zip->addFromString('database.sql', $sqlDump);
$dbSize = strlen($sqlDump);
@@ -118,8 +120,8 @@ class BackupEngine
$this->log('Starting file scan...');
$scanner = new FileScanner(
JPATH_ROOT,
$filters['exclude_dirs'] ?? [],
$filters['exclude_files'] ?? []
$excludeDirs,
$excludeFiles
);
$files = $scanner->scan();
@@ -180,6 +182,21 @@ class BackupEngine
}
}
/**
* Parse a newline-separated text field into an array of trimmed, non-empty strings.
*/
private function parseNewlineList(string $text): array
{
if (empty($text)) {
return [];
}
return array_values(array_filter(
array_map('trim', explode("\n", str_replace("\r", '', $text))),
fn($line) => $line !== ''
));
}
private function log(string $message): void
{
$this->log[] = '[' . date('H:i:s') . '] ' . $message;
@@ -34,6 +34,14 @@ HTMLHelper::_('behavior.keepalive');
</div>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php echo HTMLHelper::_('uitab.addTab', 'profileTab', 'archive', Text::_('COM_MOKOBACKUP_TAB_ARCHIVE')); ?>
<div class="row">
<div class="col-lg-9">
<?php echo $this->form->renderFieldset('archive'); ?>
</div>
</div>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php echo HTMLHelper::_('uitab.addTab', 'profileTab', 'filters', Text::_('COM_MOKOBACKUP_TAB_FILTERS')); ?>
<div class="row">
<div class="col-lg-12">
@@ -42,6 +50,16 @@ HTMLHelper::_('behavior.keepalive');
</div>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php echo HTMLHelper::_('uitab.addTab', 'profileTab', 'remote', Text::_('COM_MOKOBACKUP_TAB_REMOTE')); ?>
<div class="row">
<div class="col-lg-9">
<?php echo $this->form->renderFieldset('remote'); ?>
<?php echo $this->form->renderFieldset('ftp'); ?>
<?php echo $this->form->renderFieldset('google_drive'); ?>
</div>
</div>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php echo HTMLHelper::_('uitab.endTabSet'); ?>
</div>
@@ -0,0 +1 @@
<!DOCTYPE html><title></title>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
* Task form: select which backup profile to run.
* This form appears in System > Scheduled Tasks when creating a
* "MokoJoomBackup: Run Backup Profile" task.
-->
<form>
<fieldset name="run_profile">
<field
name="profile_id"
type="sql"
label="PLG_TASK_MOKOBACKUP_FIELD_PROFILE"
description="PLG_TASK_MOKOBACKUP_FIELD_PROFILE_DESC"
query="SELECT id AS value, title AS text FROM #__mokobackup_profiles WHERE published = 1 ORDER BY ordering ASC"
default="1"
required="true"
>
<option value="">PLG_TASK_MOKOBACKUP_SELECT_PROFILE</option>
</field>
</fieldset>
</form>
@@ -0,0 +1 @@
<!DOCTYPE html><title></title>
@@ -0,0 +1 @@
<!DOCTYPE html><title></title>
@@ -0,0 +1,12 @@
; MokoJoomBackup — Task Plugin language file (en-GB)
PLG_TASK_MOKOBACKUP="Task - MokoJoomBackup"
PLG_TASK_MOKOBACKUP_DESCRIPTION="Scheduled task plugin for MokoJoomBackup. Allows running backup profiles on a schedule via Joomla's Scheduled Tasks system."
; Task type
PLG_TASK_MOKOBACKUP_TASK_RUN_PROFILE_TITLE="MokoJoomBackup: Run Backup Profile"
PLG_TASK_MOKOBACKUP_TASK_RUN_PROFILE_DESC="Run a MokoJoomBackup backup using the selected profile. Create multiple tasks with different profiles for different backup schedules (e.g. daily full backup, hourly database-only backup)."
; Task form fields
PLG_TASK_MOKOBACKUP_FIELD_PROFILE="Backup Profile"
PLG_TASK_MOKOBACKUP_FIELD_PROFILE_DESC="Select which backup profile to run. Each profile defines backup type (full/database/files), exclusion filters, and storage settings."
PLG_TASK_MOKOBACKUP_SELECT_PROFILE="- Select Profile -"
@@ -0,0 +1,3 @@
; MokoJoomBackup — Task Plugin system language file (en-GB)
PLG_TASK_MOKOBACKUP="Task - MokoJoomBackup"
PLG_TASK_MOKOBACKUP_DESCRIPTION="Scheduled task plugin for MokoJoomBackup. Run backup profiles on a schedule via Joomla's Scheduled Tasks."
@@ -0,0 +1 @@
<!DOCTYPE html><title></title>
@@ -0,0 +1,8 @@
; MokoJoomBackup — Task Plugin language file (en-US)
PLG_TASK_MOKOBACKUP="Task - MokoJoomBackup"
PLG_TASK_MOKOBACKUP_DESCRIPTION="Scheduled task plugin for MokoJoomBackup. Allows running backup profiles on a schedule via Joomla's Scheduled Tasks system."
PLG_TASK_MOKOBACKUP_TASK_RUN_PROFILE_TITLE="MokoJoomBackup: Run Backup Profile"
PLG_TASK_MOKOBACKUP_TASK_RUN_PROFILE_DESC="Run a MokoJoomBackup backup using the selected profile. Create multiple tasks with different profiles for different backup schedules."
PLG_TASK_MOKOBACKUP_FIELD_PROFILE="Backup Profile"
PLG_TASK_MOKOBACKUP_FIELD_PROFILE_DESC="Select which backup profile to run."
PLG_TASK_MOKOBACKUP_SELECT_PROFILE="- Select Profile -"
@@ -0,0 +1,3 @@
; MokoJoomBackup — Task Plugin system language file (en-US)
PLG_TASK_MOKOBACKUP="Task - MokoJoomBackup"
PLG_TASK_MOKOBACKUP_DESCRIPTION="Scheduled task plugin for MokoJoomBackup. Run backup profiles on a schedule via Joomla's Scheduled Tasks."
@@ -0,0 +1 @@
<!DOCTYPE html><title></title>
@@ -0,0 +1,11 @@
<?php
/**
* @package MokoJoomBackup
* @subpackage plg_task_mokobackup
* @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
*/
defined('_JEXEC') or die;
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
* @package MokoJoomBackup
* @subpackage plg_task_mokobackup
* @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
-->
<extension type="plugin" group="task" method="upgrade">
<name>plg_task_mokobackup</name>
<version>01.00.00-dev</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GPL-3.0-or-later</license>
<description>PLG_TASK_MOKOBACKUP_DESCRIPTION</description>
<namespace path="src">Joomla\Plugin\Task\MokoBackup</namespace>
<files>
<filename plugin="mokobackup">mokobackup.php</filename>
<folder>services</folder>
<folder>src</folder>
<folder>forms</folder>
</files>
<languages>
<language tag="en-GB">language/en-GB/plg_task_mokobackup.ini</language>
<language tag="en-GB">language/en-GB/plg_task_mokobackup.sys.ini</language>
</languages>
</extension>
@@ -0,0 +1 @@
<!DOCTYPE html><title></title>
@@ -0,0 +1,37 @@
<?php
/**
* @package MokoJoomBackup
* @subpackage plg_task_mokobackup
* @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
*/
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 Joomla\Plugin\Task\MokoBackup\Extension\MokoBackupTask;
return new class () implements ServiceProviderInterface {
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$plugin = new MokoBackupTask(
$container->get(DispatcherInterface::class),
(array) PluginHelper::getPlugin('task', 'mokobackup')
);
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};
@@ -0,0 +1,96 @@
<?php
/**
* @package MokoJoomBackup
* @subpackage plg_task_mokobackup
* @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
*
* Joomla Scheduled Task plugin for MokoJoomBackup.
*
* Registers a "Run Backup Profile" task type with com_scheduler.
* Admins can create multiple scheduled tasks in System > Scheduled Tasks,
* each pointing to a different backup profile — just like Akeeba Backup Pro.
*/
namespace Joomla\Plugin\Task\MokoBackup\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Component\Scheduler\Administrator\Event\ExecuteTaskEvent;
use Joomla\Component\Scheduler\Administrator\Task\Status;
use Joomla\Component\Scheduler\Administrator\Traits\TaskPluginTrait;
use Joomla\Event\Event;
use Joomla\Event\SubscriberInterface;
final class MokoBackupTask extends CMSPlugin implements SubscriberInterface
{
use TaskPluginTrait;
protected $autoloadLanguage = true;
/**
* Task map — each entry registers a task type in System > Scheduled Tasks.
*
* The admin can create multiple task instances, each with its own profile_id,
* so different backup profiles run on different schedules.
*/
protected const TASKS_MAP = [
'mokobackup.run_profile' => [
'langConstPrefix' => 'PLG_TASK_MOKOBACKUP_TASK_RUN_PROFILE',
'method' => 'runBackupProfile',
'form' => 'run_profile',
],
];
public static function getSubscribedEvents(): array
{
return [
'onTaskOptionsList' => 'advertiseRoutines',
'onExecuteTask' => 'standardRoutineHandler',
'onContentPrepareForm' => 'enhanceTaskItemForm',
];
}
/**
* Execute a backup using the profile selected in the scheduled task.
*
* @param ExecuteTaskEvent $event The task execution event
*
* @return int Status::OK on success, Status::KNOCKOUT on failure
*/
private function runBackupProfile(ExecuteTaskEvent $event): int
{
$params = $event->getArgument('params');
$profileId = (int) ($params->profile_id ?? 1);
// Load the backup engine from the component
$engineFile = JPATH_ADMINISTRATOR . '/components/com_mokobackup/src/Engine/BackupEngine.php';
if (!file_exists($engineFile)) {
$this->logTask('MokoJoomBackup component not installed — cannot run backup.');
return Status::KNOCKOUT;
}
// The autoloader should handle this via namespace, but ensure class is available
if (!class_exists('\\Joomla\\Component\\MokoBackup\\Administrator\\Engine\\BackupEngine')) {
require_once $engineFile;
}
$engine = new \Joomla\Component\MokoBackup\Administrator\Engine\BackupEngine();
$result = $engine->run($profileId, 'Scheduled task backup', 'scheduled');
if ($result['success']) {
$this->logTask('Backup complete: ' . $result['message']);
return Status::OK;
}
$this->logTask('Backup failed: ' . $result['message']);
return Status::KNOCKOUT;
}
}
@@ -0,0 +1 @@
<!DOCTYPE html><title></title>
@@ -0,0 +1 @@
<!DOCTYPE html><title></title>
+1
View File
@@ -22,6 +22,7 @@
<files folder="packages">
<file type="component" id="com_mokobackup">com_mokobackup.zip</file>
<file type="plugin" id="mokobackup" group="system">plg_system_mokobackup.zip</file>
<file type="plugin" id="mokobackup" group="task">plg_task_mokobackup.zip</file>
<file type="plugin" id="mokobackup" group="webservices">plg_webservices_mokobackup.zip</file>
</files>
+11
View File
@@ -74,6 +74,17 @@ class Pkg_MokoBackupInstallerScript
$db->setQuery($query);
$db->execute();
// Enable the task plugin automatically
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('enabled') . ' = 1')
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('task'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokobackup'));
$db->setQuery($query);
$db->execute();
// Enable the webservices plugin automatically
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))