Files
ave-cms/system/App/Content/Documents/DocumentMediaFinalization.php
T
2026-07-27 12:58:44 +03:00

179 lines
5.1 KiB
PHP

<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file system/App/Content/Documents/DocumentMediaFinalization.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Content\Documents;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Frontend\Media\ThumbnailStorage;
use App\Helpers\Dir;
/** Reversible filesystem part of one document media finalization. */
class DocumentMediaFinalization
{
protected $token;
protected $moves = array();
protected $completed = false;
public function __construct($token)
{
$this->token = (string) $token;
}
public function move($sourceUrl, $targetDir)
{
$sourceUrl = self::urlPath($sourceUrl);
$source = BASEPATH . $sourceUrl;
if (!is_file($source)) {
throw new \RuntimeException('Загруженный файл не найден: ' . basename($sourceUrl));
}
$targetDir = rtrim((string) $targetDir, '/');
$absoluteTargetDir = BASEPATH . $targetDir;
if (!Dir::create($absoluteTargetDir)) {
throw new \RuntimeException('Не удалось создать папку медиа документа');
}
$name = self::targetName($source, $absoluteTargetDir, basename($sourceUrl));
$targetUrl = $targetDir . '/' . $name;
$target = BASEPATH . $targetUrl;
ThumbnailStorage::removeForSource($source);
if (!self::moveFile($source, $target)) {
throw new \RuntimeException('Не удалось переместить медиа документа: ' . basename($sourceUrl));
}
$this->moves[] = array($source, $target);
try {
$this->moveWebpCompanion($sourceUrl, $targetUrl);
} catch (\Throwable $e) {
$this->rollback();
throw $e;
}
return $targetUrl;
}
public function commit()
{
if ($this->completed) {
return;
}
$this->completed = true;
DocumentMediaDraft::finish($this->token);
}
public function rollback()
{
if ($this->completed) {
return;
}
for ($i = count($this->moves) - 1; $i >= 0; $i--) {
$source = $this->moves[$i][0];
$target = $this->moves[$i][1];
if (!is_file($target)) {
continue;
}
Dir::create(dirname($source));
self::moveFile($target, $source);
}
$this->moves = array();
}
protected function moveWebpCompanion($sourceUrl, $targetUrl)
{
$extension = strtolower(pathinfo($sourceUrl, PATHINFO_EXTENSION));
if (!in_array($extension, array('jpg', 'jpeg', 'png'), true)) {
return;
}
$sourceWebp = BASEPATH . dirname($sourceUrl) . '/' . pathinfo($sourceUrl, PATHINFO_FILENAME) . '.webp';
if (!is_file($sourceWebp)) {
return;
}
$targetWebp = BASEPATH . dirname($targetUrl) . '/' . pathinfo($targetUrl, PATHINFO_FILENAME) . '.webp';
if (is_file($targetWebp)) {
throw new \RuntimeException('WebP-копия с таким именем уже существует');
}
ThumbnailStorage::removeForSource($sourceWebp);
if (!self::moveFile($sourceWebp, $targetWebp)) {
throw new \RuntimeException('Не удалось переместить WebP-копию файла');
}
$this->moves[] = array($sourceWebp, $targetWebp);
}
protected static function targetName($source, $targetDir, $name)
{
$name = basename((string) $name);
if ($name === '' || $name === '.' || $name === '..') {
$name = 'file';
}
$target = $targetDir . '/' . $name;
$extension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$webpTarget = $targetDir . '/' . pathinfo($name, PATHINFO_FILENAME) . '.webp';
if (!file_exists($target) && (!in_array($extension, array('jpg', 'jpeg', 'png'), true) || !file_exists($webpTarget))) {
return $name;
}
$info = pathinfo($name);
$base = isset($info['filename']) && $info['filename'] !== '' ? $info['filename'] : 'file';
$suffix = '-' . substr(sha1($source), 0, 8);
$ext = isset($info['extension']) && $info['extension'] !== '' ? '.' . $info['extension'] : '';
$candidate = $base . $suffix . $ext;
$counter = 2;
while (file_exists($targetDir . '/' . $candidate)
|| (in_array($extension, array('jpg', 'jpeg', 'png'), true)
&& file_exists($targetDir . '/' . pathinfo($candidate, PATHINFO_FILENAME) . '.webp'))) {
$candidate = $base . $suffix . '-' . $counter . $ext;
$counter++;
}
return $candidate;
}
protected static function moveFile($source, $target)
{
if (@rename($source, $target)) {
return true;
}
if (!@copy($source, $target)) {
return false;
}
if (!@unlink($source)) {
@unlink($target);
return false;
}
return true;
}
protected static function urlPath($path)
{
$parsed = parse_url(html_entity_decode(trim((string) $path), ENT_QUOTES, 'UTF-8'), PHP_URL_PATH);
$path = is_string($parsed) ? rawurldecode($parsed) : (string) $path;
return '/' . ltrim(preg_replace('#/+#', '/', str_replace('\\', '/', $path)), '/');
}
}