Files
MokoSuiteBackup/source/packages/com_mokosuitebackup/src/Engine/PlaceholderResolver.php
T
Jonathan Miller ff7418721d
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 7s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: PR Check / Secret Scan (pull_request) Successful in 8s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 14s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
fix: review findings — key desc, missing changelog, [HOST] domain resolution
- Language: "encrypted" → "base64-encoded" for SSH key description
- CHANGELOG: added 3 missing bug fix entries (fields_values scope, CSRF
  token on Run Backup, SFTP showon/required)
- [HOST] placeholder: resolve domain from Joomla live_site config when
  HTTP_HOST is unavailable (CLI), instead of falling back to system
  hostname (joomla.invalid). Applied to both PlaceholderResolver and
  FolderPickerField.
2026-06-23 17:20:05 -05:00

153 lines
4.3 KiB
PHP

<?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
*
* Resolves placeholders like [HOST], [DATE], [PROFILE_NAME] in backup
* directory paths and archive filename formats.
*/
namespace Joomla\Component\MokoSuiteBackup\Administrator\Engine;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Component\MokoSuiteBackup\Administrator\Utility\BackupDirectory;
class PlaceholderResolver
{
/**
* Supported placeholders and their descriptions (for documentation).
*/
public const PLACEHOLDERS = [
'[HOST]' => 'Server hostname',
'[DATE]' => 'Date as Ymd (e.g. 20260604)',
'[TIME]' => 'Time as His (e.g. 143025)',
'[DATETIME]' => 'Date and time as Ymd_His',
'[YEAR]' => 'Four-digit year',
'[MONTH]' => 'Two-digit month',
'[DAY]' => 'Two-digit day',
'[HOUR]' => 'Two-digit hour (24h)',
'[MINUTE]' => 'Two-digit minute',
'[SECOND]' => 'Two-digit second',
'[PROFILE_ID]' => 'Backup profile ID',
'[PROFILE_NAME]' => 'Profile title (sanitized)',
'[SITE_NAME]' => 'Joomla site name (sanitized)',
'[TYPE]' => 'Backup type (full, database, files, differential)',
'[RANDOM]' => 'Random 6-character hex string',
'[DEFAULT_DIR]' => 'Default backup directory',
'[HOME]' => 'Home directory of the PHP process owner',
];
private array $replacements;
/**
* @param object $profile The backup profile object
*/
public function __construct(object $profile)
{
$now = new \DateTimeImmutable('now');
/* Resolve hostname: prefer HTTP_HOST (web), then try Joomla config (CLI), then system hostname */
$rawHost = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '';
if (empty($rawHost) || $rawHost === 'localhost') {
try {
$app = Factory::getApplication();
$liveSite = $app->get('live_site', '');
if (!empty($liveSite)) {
$parsed = parse_url($liveSite, PHP_URL_HOST);
if (!empty($parsed)) {
$rawHost = $parsed;
}
}
} catch (\Throwable $e) {
/* fallback */
}
}
if (empty($rawHost)) {
$rawHost = php_uname('n');
}
$hostname = preg_replace('/[^a-zA-Z0-9._-]/', '', $rawHost);
$siteName = '';
try {
$siteName = Factory::getApplication()->get('sitename', '');
} catch (\Throwable $e) {
// Fallback: not critical
}
$this->replacements = [
'[HOST]' => $hostname,
'[DATE]' => $now->format('Ymd'),
'[TIME]' => $now->format('His'),
'[DATETIME]' => $now->format('Ymd_His'),
'[YEAR]' => $now->format('Y'),
'[MONTH]' => $now->format('m'),
'[DAY]' => $now->format('d'),
'[HOUR]' => $now->format('H'),
'[MINUTE]' => $now->format('i'),
'[SECOND]' => $now->format('s'),
'[PROFILE_ID]' => (string) ($profile->id ?? '0'),
'[PROFILE_NAME]' => $this->sanitize($profile->title ?? 'default'),
'[SITE_NAME]' => $this->sanitize($siteName ?: 'joomla'),
'[TYPE]' => $profile->backup_type ?? 'full',
'[RANDOM]' => bin2hex(random_bytes(3)),
'[DEFAULT_DIR]' => BackupDirectory::getDefaultAbsolute(),
'[HOME]' => BackupDirectory::getHomeDirectory(),
];
}
/**
* Replace all placeholders in a string.
*
* @param string $template String containing [placeholder] tokens
*
* @return string Resolved string
*/
public function resolve(string $template): string
{
return str_replace(
array_keys($this->replacements),
array_values($this->replacements),
$template
);
}
/**
* Get the raw hostname value (for backward compatibility).
*/
public function getHostname(): string
{
return $this->replacements['[HOST]'];
}
/**
* Get the datetime tag value (for backward compatibility).
*/
public function getTag(): string
{
return $this->replacements['[DATETIME]'];
}
/**
* Sanitize a string for use in filenames/paths.
* Keeps alphanumerics, dots, hyphens, underscores. Replaces spaces with hyphens.
*/
private function sanitize(string $value): string
{
$value = str_replace(' ', '-', trim($value));
return preg_replace('/[^a-zA-Z0-9._-]/', '', $value);
}
}