Files
ave-cms/system/App/Content/Revisions/JsonRevisionStore.php
T
2026-07-30 11:56:32 +03:00

204 lines
5.9 KiB
PHP

<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file system/App/Content/Revisions/JsonRevisionStore.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\Revisions;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\SystemTables;
use App\Helpers\Json;
use DB;
/**
* Общее хранилище JSON-ревизий для сущностей с числовым владельцем.
*
* Класс не знает, как получить или применить снимок. Эти бизнес-правила
* остаются в репозитории конкретной сущности.
*/
class JsonRevisionStore
{
protected $table;
protected $ownerColumn;
protected $labels;
public function __construct($table, $ownerColumn, array $labels = array())
{
$this->table = self::identifier($table);
$this->ownerColumn = self::identifier($ownerColumn);
$this->labels = $labels;
}
public function listing($ownerId, $limit = 50)
{
return DB::query(
'SELECT * FROM ' . $this->table . ' WHERE ' . $this->ownerColumn
. '=%i ORDER BY created_at DESC,id DESC LIMIT %i',
(int) $ownerId,
max(1, min(200, (int) $limit))
)->getAll() ?: array();
}
public function one($id)
{
return DB::query(
'SELECT * FROM ' . $this->table . ' WHERE id=%i LIMIT 1',
(int) $id
)->getAssoc() ?: null;
}
public function capture($ownerId, $action, array $snapshot, $authorId = 0, $comment = '', array $extra = array(), $deduplicate = true)
{
$ownerId = (int) $ownerId;
if ($ownerId <= 0 || !$snapshot) {
return 0;
}
$json = Json::encode($snapshot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (!is_string($json)) {
throw new \RuntimeException('Не удалось создать JSON-снимок');
}
$hash = sha1($json);
if ($deduplicate) {
$last = DB::query(
'SELECT snapshot_hash FROM ' . $this->table . ' WHERE ' . $this->ownerColumn
. '=%i ORDER BY created_at DESC,id DESC LIMIT 1',
$ownerId
)->getValue();
if ($last && hash_equals((string) $last, $hash)) {
return 0;
}
}
$data = array(
$this->ownerColumn => $ownerId,
'action' => (string) $action,
'snapshot_hash' => $hash,
'snapshot_json' => $json,
'comment' => trim((string) $comment),
'author_id' => (int) $authorId,
'author_name' => self::authorName((int) $authorId),
'created_at' => time(),
);
foreach ($extra as $column => $value) {
$column = self::identifier($column);
if (array_key_exists($column, $data)) {
throw new \InvalidArgumentException('Нельзя переопределить системное поле ревизии');
}
$data[$column] = $value;
}
DB::Insert($this->table, $data);
return (int) DB::insertId();
}
public function delete($id)
{
$row = $this->one($id);
if (!$row) {
return false;
}
DB::Delete($this->table, 'id=%i', (int) $id);
return isset($row[$this->ownerColumn]) ? (int) $row[$this->ownerColumn] : 0;
}
public function deleteFor($ownerId)
{
$ownerId = (int) $ownerId;
if ($ownerId <= 0) {
return 0;
}
$count = (int) DB::query(
'SELECT COUNT(*) FROM ' . $this->table . ' WHERE ' . $this->ownerColumn . '=%i',
$ownerId
)->getValue();
DB::Delete($this->table, $this->ownerColumn . '=%i', $ownerId);
return $count;
}
public function formatRow(array $row, $withSnapshot = false)
{
$action = isset($row['action']) ? (string) $row['action'] : 'update';
$meta = isset($this->labels[$action]) ? $this->labels[$action] : array();
$label = isset($meta['label']) ? $meta['label'] : (isset($meta[0]) ? $meta[0] : $action);
$badge = isset($meta['badge']) ? $meta['badge'] : (isset($meta[1]) ? $meta[1] : 'badge-gray');
$created = isset($row['created_at']) ? (int) $row['created_at'] : 0;
$row['id'] = isset($row['id']) ? (int) $row['id'] : 0;
$row[$this->ownerColumn] = isset($row[$this->ownerColumn]) ? (int) $row[$this->ownerColumn] : 0;
$row['action'] = $action;
$row['action_label'] = (string) $label;
$row['badge'] = (string) $badge;
$row['author_id'] = isset($row['author_id']) ? (int) $row['author_id'] : 0;
$row['author_name'] = isset($row['author_name']) ? (string) $row['author_name'] : '';
$row['created_at'] = $created;
$row['created_label'] = $created > 0 ? date('d.m.Y H:i:s', $created) : '-';
$row['snapshot'] = $withSnapshot
? Json::toArray(isset($row['snapshot_json']) ? (string) $row['snapshot_json'] : '')
: null;
return $row;
}
public static function formatBytes($bytes)
{
$bytes = (int) $bytes;
if ($bytes < 1024) {
return $bytes . ' Б';
}
if ($bytes < 1048576) {
return round($bytes / 1024, 1) . ' KB';
}
return round($bytes / 1048576, 1) . ' MB';
}
protected static function authorName($id)
{
if ((int) $id <= 0) {
return '';
}
$row = DB::query(
'SELECT name,login,email FROM ' . SystemTables::table('users') . ' WHERE id=%i LIMIT 1',
(int) $id
)->getAssoc();
if (!$row) {
return '#' . (int) $id;
}
if (!empty($row['name'])) {
return (string) $row['name'];
}
if (!empty($row['login'])) {
return '@' . (string) $row['login'];
}
return !empty($row['email']) ? (string) $row['email'] : '#' . (int) $id;
}
protected static function identifier($value)
{
$value = (string) $value;
if ($value === '' || !preg_match('/^[a-zA-Z0-9_]+$/', $value)) {
throw new \InvalidArgumentException('Некорректный идентификатор хранилища ревизий');
}
return $value;
}
}