Files
MokoSuiteBackup/source/packages/com_mokobackup/src/Engine/SteppedSession.php
T
Jonathan Miller a13f7ca6a6
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
chore: rename src/ to source/ per MokoStandards convention
Update all references in Makefile, manifest.xml, .gitignore, and CI
workflows (ci-joomla, pr-check, repo-health) to use source/ as the
primary directory with src/ as a fallback for compatibility.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 08:08:33 -05:00

182 lines
3.9 KiB
PHP

<?php
/**
* @package MokoJoomBackup
* @subpackage com_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
*
* Manages state for AJAX step-based backups.
*
* On shared hosting where max_execution_time cannot be overridden,
* the backup runs as a series of small AJAX requests. Each request
* loads the session, does one chunk of work, saves state, and returns.
* The browser JS fires the next step automatically.
*
* Phases: init → database → files → finalize → upload → complete
*/
namespace Joomla\Component\MokoBackup\Administrator\Engine;
defined('_JEXEC') or die;
class SteppedSession
{
public string $sessionId;
public string $phase = 'init';
public int $recordId = 0;
public int $profileId = 0;
public string $archivePath = '';
public string $archiveName = '';
public string $description = '';
public string $origin = 'backend';
// Database phase tracking
public array $tables = [];
public int $tableIndex = 0;
public int $tablesCount = 0;
public int $dbSize = 0;
// Files phase tracking
public array $fileBatches = [];
public int $batchIndex = 0;
public int $filesCount = 0;
public int $batchSize = 200;
// Profile settings (cached so we don't re-query each step)
public string $backupType = 'full';
public string $backupDir = '';
public array $excludeDirs = [];
public array $excludeFiles = [];
public array $excludeTables = [];
public string $remoteStorage = 'none';
public bool $includeMokoRestore = false;
public bool $remoteKeepLocal = true;
// Progress
public int $totalSteps = 0;
public int $currentStep = 0;
public string $statusMessage = '';
public array $log = [];
private static function getSessionDir(): string
{
$dir = JPATH_ROOT . '/tmp/mokobackup-sessions';
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
return $dir;
}
private static function getSessionPath(string $sessionId): string
{
// Sanitize session ID to prevent path traversal
$safe = preg_replace('/[^a-zA-Z0-9_-]/', '', $sessionId);
return self::getSessionDir() . '/' . $safe . '.json';
}
/**
* Create a new session.
*/
public static function create(): self
{
$session = new self();
$session->sessionId = 'mb_' . bin2hex(random_bytes(8));
return $session;
}
/**
* Load an existing session from disk.
*/
public static function load(string $sessionId): ?self
{
$path = self::getSessionPath($sessionId);
if (!is_file($path)) {
return null;
}
$data = json_decode(file_get_contents($path), true);
if (!$data) {
return null;
}
$session = new self();
foreach ($data as $key => $value) {
if (property_exists($session, $key)) {
$session->$key = $value;
}
}
return $session;
}
/**
* Save session state to disk.
*/
public function save(): void
{
$path = self::getSessionPath($this->sessionId);
file_put_contents($path, json_encode(get_object_vars($this), JSON_PRETTY_PRINT));
}
/**
* Delete session file.
*/
public function destroy(): void
{
$path = self::getSessionPath($this->sessionId);
if (is_file($path)) {
@unlink($path);
}
}
/**
* Add a log entry.
*/
public function log(string $message): void
{
$this->log[] = '[' . date('H:i:s') . '] ' . $message;
}
/**
* Calculate progress percentage.
*/
public function getProgress(): int
{
if ($this->totalSteps <= 0) {
return 0;
}
return min(100, (int) round(($this->currentStep / $this->totalSteps) * 100));
}
/**
* Clean up old session files (older than 24 hours).
*/
public static function cleanupOldSessions(): void
{
$dir = self::getSessionDir();
if (!is_dir($dir)) {
return;
}
$cutoff = time() - 86400;
foreach (glob($dir . '/mb_*.json') as $file) {
if (filemtime($file) < $cutoff) {
@unlink($file);
}
}
}
}