Files
MokoSuiteBackup/source/packages/com_mokobackup/src/Engine/FtpUploader.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

165 lines
4.1 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
*/
namespace Joomla\Component\MokoBackup\Administrator\Engine;
defined('_JEXEC') or die;
class FtpUploader implements RemoteUploaderInterface
{
private string $host;
private int $port;
private string $username;
private string $password;
private string $remotePath;
private bool $passive;
private bool $ssl;
public function __construct(object $profile)
{
$this->host = $profile->ftp_host ?? '';
$this->port = (int) ($profile->ftp_port ?? 21);
$this->username = $profile->ftp_username ?? '';
$this->password = $profile->ftp_password ?? '';
$this->remotePath = rtrim($profile->ftp_path ?? '/backups', '/');
$this->passive = (bool) ($profile->ftp_passive ?? true);
$this->ssl = (bool) ($profile->ftp_ssl ?? false);
}
public function upload(string $localPath, string $remoteName): array
{
if (!extension_loaded('ftp')) {
return ['success' => false, 'message' => 'PHP ext-ftp is required for FTP uploads. Enable it in php.ini.'];
}
if (empty($this->host)) {
return ['success' => false, 'message' => 'FTP host is not configured'];
}
$conn = $this->connect();
if (!$conn) {
return ['success' => false, 'message' => 'Failed to connect to FTP server ' . $this->host . ':' . $this->port];
}
try {
if (!@ftp_login($conn, $this->username, $this->password)) {
throw new \RuntimeException('FTP login failed for user: ' . $this->username);
}
if ($this->passive) {
ftp_pasv($conn, true);
}
// Ensure remote directory exists (create recursively)
$this->ensureRemoteDir($conn, $this->remotePath);
$remoteFile = $this->remotePath . '/' . $remoteName;
if (!@ftp_put($conn, $remoteFile, $localPath, FTP_BINARY)) {
throw new \RuntimeException('Failed to upload file to: ' . $remoteFile);
}
// Verify upload by checking remote file size
$remoteSize = @ftp_size($conn, $remoteFile);
$localSize = filesize($localPath);
if ($remoteSize >= 0 && $remoteSize !== $localSize) {
throw new \RuntimeException(
'Size mismatch: local=' . $localSize . ' remote=' . $remoteSize
);
}
return [
'success' => true,
'message' => 'Uploaded to FTP: ' . $remoteFile,
'remote_path' => $remoteFile,
];
} catch (\Throwable $e) {
return ['success' => false, 'message' => 'FTP upload failed: ' . $e->getMessage()];
} finally {
@ftp_close($conn);
}
}
public function testConnection(): array
{
if (empty($this->host)) {
return ['success' => false, 'message' => 'FTP host is not configured'];
}
$conn = $this->connect();
if (!$conn) {
return ['success' => false, 'message' => 'Cannot connect to ' . $this->host . ':' . $this->port];
}
try {
if (!@ftp_login($conn, $this->username, $this->password)) {
return ['success' => false, 'message' => 'Login failed for user: ' . $this->username];
}
if ($this->passive) {
ftp_pasv($conn, true);
}
$pwd = @ftp_pwd($conn);
return [
'success' => true,
'message' => 'Connected to ' . $this->host . ' (cwd: ' . $pwd . ')',
];
} finally {
@ftp_close($conn);
}
}
/**
* @return resource|false
*/
private function connect()
{
$timeout = 30;
if ($this->ssl) {
return @ftp_ssl_connect($this->host, $this->port, $timeout);
}
return @ftp_connect($this->host, $this->port, $timeout);
}
/**
* Recursively create remote directories.
*
* @param resource $conn FTP connection
* @param string $path Remote directory path
*/
private function ensureRemoteDir($conn, string $path): void
{
$parts = explode('/', trim($path, '/'));
$current = '';
foreach ($parts as $part) {
$current .= '/' . $part;
if (@ftp_chdir($conn, $current)) {
continue;
}
if (!@ftp_mkdir($conn, $current)) {
throw new \RuntimeException('Cannot create remote directory: ' . $current);
}
}
// Return to root
@ftp_chdir($conn, '/');
}
}