mirror of
https://github.com/avecms/AVE.cms.git
synced 2026-08-01 08:45:44 +00:00
203 lines
5.9 KiB
PHP
203 lines
5.9 KiB
PHP
<?php
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------------------
|
|
| AVE.cms
|
|
|--------------------------------------------------------------------------------------
|
|
| @package AVE.cms
|
|
| @file system/App/Content/Documents/DocumentMediaDraft.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\Common\Session;
|
|
use App\Helpers\Dir;
|
|
|
|
/** Session-bound staging area used before a document save has a stable ID. */
|
|
class DocumentMediaDraft
|
|
{
|
|
const ROOT = '/uploads/.drafts';
|
|
const SESSION_KEY = '_document_media_drafts';
|
|
const TTL = 172800;
|
|
|
|
public static function issue($actorId, $documentId = 0)
|
|
{
|
|
self::cleanupExpired();
|
|
$token = bin2hex(random_bytes(20));
|
|
$drafts = self::sessionDrafts();
|
|
$drafts[$token] = array(
|
|
'actor_id' => (int) $actorId,
|
|
'document_id' => max(0, (int) $documentId),
|
|
'created_at' => time(),
|
|
'touched_at' => time(),
|
|
);
|
|
Session::set(self::SESSION_KEY, $drafts);
|
|
return $token;
|
|
}
|
|
|
|
public static function uploadDirectory($token, $fieldId, $actorId, $documentId = 0)
|
|
{
|
|
self::assertOwned($token, $actorId, $documentId);
|
|
$fieldId = (int) $fieldId;
|
|
if ($fieldId <= 0) {
|
|
throw new \RuntimeException('Не указано поле для загрузки');
|
|
}
|
|
|
|
self::touch($token);
|
|
return self::directory($token, $fieldId);
|
|
}
|
|
|
|
public static function directory($token, $fieldId = 0)
|
|
{
|
|
$token = self::normalizeToken($token);
|
|
$path = self::ROOT . '/' . $token;
|
|
return (int) $fieldId > 0 ? $path . '/' . (int) $fieldId : $path;
|
|
}
|
|
|
|
public static function assertOwned($token, $actorId, $documentId = 0)
|
|
{
|
|
$token = self::normalizeToken($token);
|
|
$drafts = self::sessionDrafts();
|
|
if (!isset($drafts[$token]) || !is_array($drafts[$token])) {
|
|
throw new \RuntimeException('Сессия загрузки файлов устарела. Обновите редактор документа.');
|
|
}
|
|
|
|
$draft = $drafts[$token];
|
|
if ((int) $draft['actor_id'] !== (int) $actorId) {
|
|
throw new \RuntimeException('Сессия загрузки принадлежит другому пользователю');
|
|
}
|
|
|
|
$boundDocument = isset($draft['document_id']) ? (int) $draft['document_id'] : 0;
|
|
if ($boundDocument > 0 && $boundDocument !== (int) $documentId) {
|
|
throw new \RuntimeException('Сессия загрузки принадлежит другому документу');
|
|
}
|
|
|
|
if ((int) (isset($draft['touched_at']) ? $draft['touched_at'] : $draft['created_at']) < time() - self::TTL) {
|
|
self::forget($token, true);
|
|
throw new \RuntimeException('Сессия загрузки файлов устарела. Обновите редактор документа.');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static function ownsPath($token, $fieldId, $path)
|
|
{
|
|
$path = self::urlPath($path);
|
|
$prefix = self::directory($token, $fieldId) . '/';
|
|
return strpos($path, $prefix) === 0 && strpos(substr($path, strlen($prefix)), '/') === false;
|
|
}
|
|
|
|
public static function isDraftPath($path)
|
|
{
|
|
$path = self::urlPath($path);
|
|
return strpos($path, self::ROOT . '/') === 0;
|
|
}
|
|
|
|
public static function finish($token)
|
|
{
|
|
if (!self::validToken($token)) {
|
|
return;
|
|
}
|
|
|
|
self::forget((string) $token, true);
|
|
}
|
|
|
|
public static function cleanupExpired()
|
|
{
|
|
$cutoff = time() - self::TTL;
|
|
$drafts = self::sessionDrafts();
|
|
$sessionChanged = false;
|
|
foreach ($drafts as $token => $draft) {
|
|
$touchedAt = is_array($draft) && isset($draft['touched_at'])
|
|
? (int) $draft['touched_at']
|
|
: (is_array($draft) && isset($draft['created_at']) ? (int) $draft['created_at'] : 0);
|
|
if (!self::validToken($token) || $touchedAt < $cutoff) {
|
|
unset($drafts[$token]);
|
|
$sessionChanged = true;
|
|
}
|
|
}
|
|
|
|
if ($sessionChanged) {
|
|
Session::set(self::SESSION_KEY, $drafts);
|
|
}
|
|
|
|
$root = BASEPATH . self::ROOT;
|
|
if (!is_dir($root)) {
|
|
return;
|
|
}
|
|
|
|
foreach (scandir($root) ?: array() as $entry) {
|
|
if ($entry === '.' || $entry === '..' || !self::validToken($entry)) {
|
|
continue;
|
|
}
|
|
|
|
$dir = $root . '/' . $entry;
|
|
$modified = @filemtime($dir);
|
|
if (is_dir($dir) && $modified !== false && $modified < $cutoff) {
|
|
Dir::delete($dir);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected static function touch($token)
|
|
{
|
|
$drafts = self::sessionDrafts();
|
|
if (isset($drafts[$token])) {
|
|
$drafts[$token]['touched_at'] = time();
|
|
Session::set(self::SESSION_KEY, $drafts);
|
|
}
|
|
|
|
$dir = BASEPATH . self::directory($token);
|
|
if (Dir::create($dir)) {
|
|
@touch($dir);
|
|
}
|
|
}
|
|
|
|
protected static function forget($token, $removeFiles)
|
|
{
|
|
$drafts = self::sessionDrafts();
|
|
unset($drafts[$token]);
|
|
Session::set(self::SESSION_KEY, $drafts);
|
|
$dir = BASEPATH . self::directory($token);
|
|
if ($removeFiles && is_dir($dir)) {
|
|
Dir::delete($dir);
|
|
}
|
|
}
|
|
|
|
protected static function sessionDrafts()
|
|
{
|
|
$drafts = Session::get(self::SESSION_KEY);
|
|
return is_array($drafts) ? $drafts : array();
|
|
}
|
|
|
|
protected static function normalizeToken($token)
|
|
{
|
|
$token = (string) $token;
|
|
if (!self::validToken($token)) {
|
|
throw new \RuntimeException('Некорректная сессия загрузки файлов');
|
|
}
|
|
|
|
return $token;
|
|
}
|
|
|
|
protected static function validToken($token)
|
|
{
|
|
return is_string($token) && (bool) preg_match('/^[a-f0-9]{40}$/', $token);
|
|
}
|
|
|
|
protected static function urlPath($path)
|
|
{
|
|
$path = html_entity_decode(trim((string) $path), ENT_QUOTES, 'UTF-8');
|
|
$parsed = parse_url($path, PHP_URL_PATH);
|
|
$path = is_string($parsed) ? rawurldecode($parsed) : $path;
|
|
$path = preg_replace('#/+#', '/', str_replace('\\', '/', $path));
|
|
return '/' . ltrim($path, '/');
|
|
}
|
|
}
|