mirror of
https://github.com/openvk/openvk
synced 2024-12-23 00:51:03 +03:00
Логи
This commit is contained in:
parent
a2384cc231
commit
fab54f37c8
18 changed files with 639 additions and 11 deletions
135
DBEntity.updated.php
Normal file
135
DBEntity.updated.php
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace Chandler\Database;
|
||||||
|
use Chandler\Database\DatabaseConnection;
|
||||||
|
use Nette\Database\Table\Selection;
|
||||||
|
use Nette\Database\Table\ActiveRow;
|
||||||
|
use Nette\InvalidStateException as ISE;
|
||||||
|
use openvk\Web\Models\Repositories\CurrentUser;
|
||||||
|
use openvk\Web\Models\Repositories\Logs;
|
||||||
|
|
||||||
|
|
||||||
|
abstract class DBEntity
|
||||||
|
{
|
||||||
|
protected $record;
|
||||||
|
protected $changes;
|
||||||
|
protected $deleted;
|
||||||
|
protected $user;
|
||||||
|
|
||||||
|
protected $tableName;
|
||||||
|
|
||||||
|
function __construct(?ActiveRow $row = NULL)
|
||||||
|
{
|
||||||
|
if(is_null($row)) return;
|
||||||
|
|
||||||
|
$_table = $row->getTable()->getName();
|
||||||
|
if($_table !== $this->tableName)
|
||||||
|
throw new ISE("Invalid data supplied for model: table $_table is not compatible with table" . $this->tableName);
|
||||||
|
|
||||||
|
$this->record = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __call(string $fName, array $args)
|
||||||
|
{
|
||||||
|
if(substr($fName, 0, 3) === "set") {
|
||||||
|
$field = mb_strtolower(substr($fName, 3));
|
||||||
|
$this->stateChanges($field, $args[0]);
|
||||||
|
} else {
|
||||||
|
throw new \Error("Call to undefined method " . get_class($this) . "::$fName");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTable(): Selection
|
||||||
|
{
|
||||||
|
return DatabaseConnection::i()->getContext()->table($this->tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRecord(): ?ActiveRow
|
||||||
|
{
|
||||||
|
return $this->record;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function stateChanges(string $column, $value): void
|
||||||
|
{
|
||||||
|
if(!is_null($this->record))
|
||||||
|
$t = $this->record->{$column}; #Test if column exists
|
||||||
|
|
||||||
|
$this->changes[$column] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getId()
|
||||||
|
{
|
||||||
|
return $this->getRecord()->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDeleted(): bool
|
||||||
|
{
|
||||||
|
return (bool) $this->getRecord()->deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unwrap(): object
|
||||||
|
{
|
||||||
|
return (object) $this->getRecord()->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete(bool $softly = true): void
|
||||||
|
{
|
||||||
|
$user = CurrentUser::i()->getUser();
|
||||||
|
$user_id = is_null($user) ? (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"] : $user->getId();
|
||||||
|
|
||||||
|
if(is_null($this->record))
|
||||||
|
throw new ISE("Can't delete a model, that hasn't been flushed to DB. Have you forgotten to call save() first?");
|
||||||
|
|
||||||
|
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 2, $this->record->toArray(), $this->changes);
|
||||||
|
|
||||||
|
if($softly) {
|
||||||
|
$this->record = $this->getTable()->where("id", $this->record->id)->update(["deleted" => true]);
|
||||||
|
} else {
|
||||||
|
$this->record->delete();
|
||||||
|
$this->deleted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function undelete(): void
|
||||||
|
{
|
||||||
|
if(is_null($this->record))
|
||||||
|
throw new ISE("Can't undelete a model, that hasn't been flushed to DB. Have you forgotten to call save() first?");
|
||||||
|
|
||||||
|
$user = CurrentUser::i()->getUser();
|
||||||
|
$user_id = is_null($user) ? (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"] : $user->getId();
|
||||||
|
|
||||||
|
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 3, $this->record->toArray(), ["deleted" => false]);
|
||||||
|
|
||||||
|
$this->getTable()->where("id", $this->record->id)->update(["deleted" => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(?bool $log = true): void
|
||||||
|
{
|
||||||
|
if ($log) {
|
||||||
|
$user = CurrentUser::i()->getUser();
|
||||||
|
$user_id = is_null($user) ? (int)OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"] : $user->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_null($this->record)) {
|
||||||
|
$this->record = $this->getTable()->insert($this->changes);
|
||||||
|
|
||||||
|
if ($log && $this->getTable()->getName() !== "logs") {
|
||||||
|
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 0, $this->record->toArray(), $this->changes);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($log && $this->getTable()->getName() !== "logs") {
|
||||||
|
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 1, $this->record->toArray(), $this->changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->deleted) {
|
||||||
|
$this->record = $this->getTable()->insert((array)$this->record);
|
||||||
|
} else {
|
||||||
|
$this->getTable()->get($this->record->id)->update($this->changes);
|
||||||
|
$this->record = $this->getTable()->get($this->record->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->changes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
use \Nette\SmartObject;
|
||||||
|
}
|
|
@ -85,4 +85,9 @@ class Comment extends Post
|
||||||
}
|
}
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getURL(): string
|
||||||
|
{
|
||||||
|
return "/wall" . $this->getTarget()->getPrettyId() . "#_comment" . $this->getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ class IP extends RowModel
|
||||||
$this->stateChanges("rate_limit_counter", $aCounter);
|
$this->stateChanges("rate_limit_counter", $aCounter);
|
||||||
$this->stateChanges("rate_limit_violation_counter_start", $vCounterSessionStart);
|
$this->stateChanges("rate_limit_violation_counter_start", $vCounterSessionStart);
|
||||||
$this->stateChanges("rate_limit_violation_counter", $vCounter);
|
$this->stateChanges("rate_limit_violation_counter", $vCounter);
|
||||||
$this->save();
|
$this->save(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,11 +105,11 @@ class IP extends RowModel
|
||||||
$this->stateChanges("ip", $ip);
|
$this->stateChanges("ip", $ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(): void
|
function save($log): void
|
||||||
{
|
{
|
||||||
if(is_null($this->getRecord()))
|
if(is_null($this->getRecord()))
|
||||||
$this->stateChanges("first_seen", time());
|
$this->stateChanges("first_seen", time());
|
||||||
|
|
||||||
parent::save();
|
parent::save($log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
190
Web/Models/Entities/Log.php
Normal file
190
Web/Models/Entities/Log.php
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Models\Entities;
|
||||||
|
use Chandler\Database\DatabaseConnection;
|
||||||
|
use openvk\Web\Models\Repositories\GeodbCities;
|
||||||
|
use openvk\Web\Models\Repositories\GeodbCountries;
|
||||||
|
use openvk\Web\Models\Repositories\GeodbEducation;
|
||||||
|
use openvk\Web\Models\Repositories\GeodbSpecializations;
|
||||||
|
use openvk\Web\Models\Repositories\Users;
|
||||||
|
use openvk\Web\Models\RowModel;
|
||||||
|
use openvk\Web\Util\DateTime;
|
||||||
|
|
||||||
|
class Log extends RowModel
|
||||||
|
{
|
||||||
|
protected $tableName = "logs";
|
||||||
|
|
||||||
|
function getId(): int
|
||||||
|
{
|
||||||
|
return (int) $this->getRecord()->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUser(): ?User
|
||||||
|
{
|
||||||
|
return (new Users)->get((int) $this->getRecord()->user);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObjectTable(): string
|
||||||
|
{
|
||||||
|
return $this->getRecord()->object_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObjectId(): int
|
||||||
|
{
|
||||||
|
return $this->getRecord()->object_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObject()
|
||||||
|
{
|
||||||
|
$model = $this->getRecord()->object_model;
|
||||||
|
return new $model(DatabaseConnection::i()->getContext()->table($this->getObjectTable())->get($this->getObjectId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeRaw(): int
|
||||||
|
{
|
||||||
|
return $this->getRecord()->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getType(): string
|
||||||
|
{
|
||||||
|
return ["добавил", "отредактировал", "удалил", "восстановил"][$this->getTypeRaw()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeNom(): string
|
||||||
|
{
|
||||||
|
return ["Создание", "Редактирование", "Удаление", "Восстановление"][$this->getTypeRaw()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObjectType(): string
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"albums" => "Альбом",
|
||||||
|
"groups" => "Сообщество",
|
||||||
|
"profiles" => "Профиль",
|
||||||
|
"comments" => "Комментарий",
|
||||||
|
"ip" => "IP-адрес",
|
||||||
|
"posts" => "Запись",
|
||||||
|
"tickets" => "Вопрос",
|
||||||
|
"tickets_comments" => "Комментарий к тикету",
|
||||||
|
][$this->getRecord()->object_table] ?? $this->getRecord()->object_model;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObjectName(): string
|
||||||
|
{
|
||||||
|
$object = $this->getObject();
|
||||||
|
if (method_exists($object, 'getCanonicalName'))
|
||||||
|
return $object->getCanonicalName();
|
||||||
|
else return "[#" . $this->getObjectId() . "] " . $this->getObjectType();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLogsText(): string
|
||||||
|
{
|
||||||
|
return $this->getRecord()->logs_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObjectURL(): string
|
||||||
|
{
|
||||||
|
$object = $this->getObject();
|
||||||
|
if (method_exists($object, "getURL") && $this->getObjectTable() !== "videos")
|
||||||
|
return $this->getObject()->getURL();
|
||||||
|
else
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObjectAvatar(): ?string
|
||||||
|
{
|
||||||
|
$object = $this->getObject();
|
||||||
|
if (method_exists($object, 'getAvatarURL'))
|
||||||
|
return $object->getAvatarURL("normal");
|
||||||
|
else return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOldValue(): ?array
|
||||||
|
{
|
||||||
|
return (array) json_decode($this->getRecord()->xdiff_old, true, JSON_UNESCAPED_UNICODE) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNewValue(): ?array
|
||||||
|
{
|
||||||
|
return (array) json_decode($this->getRecord()->xdiff_new, true, JSON_UNESCAPED_UNICODE) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTime(): DateTime
|
||||||
|
{
|
||||||
|
return new DateTime($this->getRecord()->ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function diff($old, $new): array
|
||||||
|
{
|
||||||
|
$matrix = array();
|
||||||
|
$maxlen = 0;
|
||||||
|
foreach ($old as $oindex => $ovalue) {
|
||||||
|
$nkeys = array_keys($new, $ovalue);
|
||||||
|
foreach ($nkeys as $nindex) {
|
||||||
|
$matrix[$oindex][$nindex] = isset($matrix[$oindex - 1][$nindex - 1]) ?
|
||||||
|
$matrix[$oindex - 1][$nindex - 1] + 1 : 1;
|
||||||
|
if ($matrix[$oindex][$nindex] > $maxlen) {
|
||||||
|
$maxlen = $matrix[$oindex][$nindex];
|
||||||
|
$omax = $oindex + 1 - $maxlen;
|
||||||
|
$nmax = $nindex + 1 - $maxlen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($maxlen == 0) return array(array('d' => $old, 'i' => $new));
|
||||||
|
return array_merge(
|
||||||
|
$this->diff(array_slice($old, 0, $omax), array_slice($new, 0, $nmax)),
|
||||||
|
array_slice($new, $nmax, $maxlen),
|
||||||
|
$this->diff(array_slice($old, $omax + $maxlen), array_slice($new, $nmax + $maxlen)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function htmlDiff($old, $new): string
|
||||||
|
{
|
||||||
|
$ret = '';
|
||||||
|
$diff = $this->diff(preg_split("/[\s]+/", $old), preg_split("/[\s]+/", $new));
|
||||||
|
foreach ($diff as $k) {
|
||||||
|
if (is_array($k))
|
||||||
|
$ret .= (!empty($k['d']) ? "<del>" . implode(' ', $k['d']) . "</del> " : '') .
|
||||||
|
(!empty($k['i']) ? "<ins>" . implode(' ', $k['i']) . "</ins> " : '');
|
||||||
|
else $ret .= $k . ' ';
|
||||||
|
}
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChanges(): array
|
||||||
|
{
|
||||||
|
$result = $this->getOldValue();
|
||||||
|
$_changes = [];
|
||||||
|
|
||||||
|
if ($this->getTypeRaw() === 1) { // edit
|
||||||
|
$changes = $this->getNewValue();
|
||||||
|
|
||||||
|
foreach ($changes as $field => $value) {
|
||||||
|
$new_value = xdiff_string_patch((string) $result[$field], (string) $value);
|
||||||
|
$_changes[$field] = [
|
||||||
|
"field" => $field,
|
||||||
|
"old_value" => $result[$field],
|
||||||
|
"new_value" => strlen($new_value) > 0 ? $new_value : "(empty)",
|
||||||
|
"ts" => $this->getTime(),
|
||||||
|
"diff" => $this->htmlDiff((string) $result[$field], (string) $new_value)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else if ($this->getTypeRaw() === 0) { // create
|
||||||
|
foreach ($result as $field => $value) {
|
||||||
|
$_changes[$field] = [
|
||||||
|
"field" => $field,
|
||||||
|
"old_value" => $value,
|
||||||
|
"ts" => $this->getTime()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else if ($this->getTypeRaw() === 2) { // delete
|
||||||
|
$_changes[] = [
|
||||||
|
"field" => "deleted",
|
||||||
|
"old_value" => 0,
|
||||||
|
"new_value" => 1,
|
||||||
|
"ts" => $this->getTime(),
|
||||||
|
"diff" => $this->htmlDiff("0", "1")
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $_changes;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1013,7 +1013,7 @@ class User extends RowModel
|
||||||
{
|
{
|
||||||
$this->setOnline(time());
|
$this->setOnline(time());
|
||||||
$this->setClient_name($platform);
|
$this->setClient_name($platform);
|
||||||
$this->save();
|
$this->save(false);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1031,7 +1031,7 @@ class User extends RowModel
|
||||||
|
|
||||||
function adminNotify(string $message): bool
|
function adminNotify(string $message): bool
|
||||||
{
|
{
|
||||||
$admId = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"];
|
$admId = (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"];
|
||||||
if(!$admId)
|
if(!$admId)
|
||||||
return false;
|
return false;
|
||||||
else if(is_null($admin = (new Users)->get($admId)))
|
else if(is_null($admin = (new Users)->get($admId)))
|
||||||
|
|
31
Web/Models/Repositories/CurrentUser.php
Normal file
31
Web/Models/Repositories/CurrentUser.php
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Models\Repositories;
|
||||||
|
use openvk\Web\Models\Entities\User;
|
||||||
|
|
||||||
|
class CurrentUser
|
||||||
|
{
|
||||||
|
private static $instance = null;
|
||||||
|
private $user;
|
||||||
|
|
||||||
|
public function __construct(?User $user = NULL)
|
||||||
|
{
|
||||||
|
if ($user)
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get($user)
|
||||||
|
{
|
||||||
|
if (self::$instance === null) self::$instance = new self($user);
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function i()
|
||||||
|
{
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
}
|
94
Web/Models/Repositories/Logs.php
Normal file
94
Web/Models/Repositories/Logs.php
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Models\Repositories;
|
||||||
|
use Chandler\Database\DatabaseConnection;
|
||||||
|
use Nette\Database\Table\ActiveRow;
|
||||||
|
use openvk\Web\Models\Entities\Country;
|
||||||
|
use openvk\Web\Models\Entities\Editor;
|
||||||
|
use openvk\Web\Models\Entities\Log;
|
||||||
|
use openvk\Web\Models\Entities\User;
|
||||||
|
|
||||||
|
class Logs
|
||||||
|
{
|
||||||
|
private $context;
|
||||||
|
private $logs;
|
||||||
|
|
||||||
|
function __construct()
|
||||||
|
{
|
||||||
|
$this->context = DatabaseConnection::i()->getContext();
|
||||||
|
$this->logs = $this->context->table("logs");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toLog(?ActiveRow $ar): ?Log
|
||||||
|
{
|
||||||
|
return is_null($ar) ? NULL : new Log($ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(int $id): ?Log
|
||||||
|
{
|
||||||
|
return $this->toLog($this->logs->get($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function create(int $user, string $table, string $model, int $type, $object, $changes): void
|
||||||
|
{
|
||||||
|
if (OPENVK_ROOT_CONF["openvk"]["preferences"]["logs"] === true) {
|
||||||
|
$fobject = (is_array($object) ? $object : $object->unwrap());
|
||||||
|
$nobject = [];
|
||||||
|
$_changes = [];
|
||||||
|
|
||||||
|
if ($type === 1) {
|
||||||
|
foreach ($changes as $field => $value) {
|
||||||
|
$nobject[$field] = $fobject[$field];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_diff_assoc($nobject, $changes) as $field => $value) {
|
||||||
|
if (str_starts_with($field, "rate_limit")) continue;
|
||||||
|
if ($field === "online") continue;
|
||||||
|
$_changes[$field] = xdiff_string_diff((string)$nobject[$field], (string)$changes[$field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($_changes) === 0) return;
|
||||||
|
} else if ($type === 0) { // if new
|
||||||
|
$nobject = $fobject;
|
||||||
|
foreach ($fobject as $field => $value) {
|
||||||
|
$_changes[$field] = xdiff_string_diff("", (string)$value);
|
||||||
|
}
|
||||||
|
} else if ($type === 2 || $type === 3) { // if deleting or restoring
|
||||||
|
$_changes["deleted"] = (int)($type === 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
$log = new Log;
|
||||||
|
$log->setUser($user);
|
||||||
|
$log->setType($type);
|
||||||
|
$log->setObject_Table($table);
|
||||||
|
$log->setObject_Model($model);
|
||||||
|
$log->setObject_Id(is_array($object) ? $object["id"] : $object->getId());
|
||||||
|
$log->setXdiff_Old(json_encode($nobject));
|
||||||
|
$log->setXdiff_New(json_encode($_changes));
|
||||||
|
$log->setTs(time());
|
||||||
|
$log->save(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function find(string $query, array $pars = [], string $sort = "id DESC", int $page = 1, ?int $perPage = NULL): \Traversable
|
||||||
|
{
|
||||||
|
$query = "%$query%";
|
||||||
|
$result = $this->logs->where("id LIKE ? OR object_table LIKE ?", $query, $query);
|
||||||
|
|
||||||
|
return new Util\EntityStream("Log", $result->order($sort));
|
||||||
|
}
|
||||||
|
|
||||||
|
function search($filter): \Traversable
|
||||||
|
{
|
||||||
|
foreach ($this->logs->where($filter)->order("id DESC") as $log)
|
||||||
|
yield new Log($log);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypes(): array
|
||||||
|
{
|
||||||
|
$types = [];
|
||||||
|
foreach ($this->context->query("SELECT DISTINCT(`object_model`) AS `object_model` FROM `logs`")->fetchAll() as $type)
|
||||||
|
$types[] = str_replace("openvk\\Web\\Models\\Entities\\", "", $type->object_model);
|
||||||
|
|
||||||
|
return $types;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
namespace openvk\Web\Presenters;
|
namespace openvk\Web\Presenters;
|
||||||
use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User, BannedLink};
|
use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User, BannedLink};
|
||||||
use openvk\Web\Models\Repositories\{ChandlerGroups, ChandlerUsers, Users, Clubs, Vouchers, Gifts, BannedLinks};
|
use openvk\Web\Models\Repositories\{ChandlerGroups, ChandlerUsers, Logs, Users, Clubs, Vouchers, Gifts, BannedLinks};
|
||||||
use Chandler\Database\DatabaseConnection;
|
use Chandler\Database\DatabaseConnection;
|
||||||
|
|
||||||
final class AdminPresenter extends OpenVKPresenter
|
final class AdminPresenter extends OpenVKPresenter
|
||||||
|
@ -12,8 +12,9 @@ final class AdminPresenter extends OpenVKPresenter
|
||||||
private $gifts;
|
private $gifts;
|
||||||
private $bannedLinks;
|
private $bannedLinks;
|
||||||
private $chandlerGroups;
|
private $chandlerGroups;
|
||||||
|
private $logs;
|
||||||
|
|
||||||
function __construct(Users $users, Clubs $clubs, Vouchers $vouchers, Gifts $gifts, BannedLinks $bannedLinks, ChandlerGroups $chandlerGroups)
|
function __construct(Users $users, Clubs $clubs, Vouchers $vouchers, Gifts $gifts, BannedLinks $bannedLinks, ChandlerGroups $chandlerGroups, Logs $logs)
|
||||||
{
|
{
|
||||||
$this->users = $users;
|
$this->users = $users;
|
||||||
$this->clubs = $clubs;
|
$this->clubs = $clubs;
|
||||||
|
@ -21,6 +22,7 @@ final class AdminPresenter extends OpenVKPresenter
|
||||||
$this->gifts = $gifts;
|
$this->gifts = $gifts;
|
||||||
$this->bannedLinks = $bannedLinks;
|
$this->bannedLinks = $bannedLinks;
|
||||||
$this->chandlerGroups = $chandlerGroups;
|
$this->chandlerGroups = $chandlerGroups;
|
||||||
|
$this->logs = $logs;
|
||||||
|
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
@ -550,4 +552,43 @@ final class AdminPresenter extends OpenVKPresenter
|
||||||
|
|
||||||
$this->redirect("/admin/users/id" . $user->getId());
|
$this->redirect("/admin/users/id" . $user->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderLogs(): void
|
||||||
|
{
|
||||||
|
$filter = [];
|
||||||
|
|
||||||
|
if ($this->queryParam("id")) {
|
||||||
|
$id = (int) $this->queryParam("id");
|
||||||
|
$filter["id"] = $id;
|
||||||
|
$this->template->id = $id;
|
||||||
|
}
|
||||||
|
if ($this->queryParam("type") !== NULL && $this->queryParam("type") !== "any") {
|
||||||
|
$type = in_array($this->queryParam("type"), [0, 1, 2, 3]) ? (int) $this->queryParam("type") : 0;
|
||||||
|
$filter["type"] = $type;
|
||||||
|
$this->template->type = $type;
|
||||||
|
}
|
||||||
|
if ($this->queryParam("uid")) {
|
||||||
|
$user = (int) $this->queryParam("uid");
|
||||||
|
$filter["user"] = $user;
|
||||||
|
$this->template->user = $user;
|
||||||
|
}
|
||||||
|
if ($this->queryParam("obj_id")) {
|
||||||
|
$obj_id = (int) $this->queryParam("obj_id");
|
||||||
|
$filter["object_id"] = $obj_id;
|
||||||
|
$this->template->obj_id = $obj_id;
|
||||||
|
}
|
||||||
|
if ($this->queryParam("obj_type") !== NULL && $this->queryParam("obj_type") !== "any") {
|
||||||
|
$obj_type = "openvk\\Web\\Models\\Entities\\" . $this->queryParam("obj_type");
|
||||||
|
$filter["object_model"] = $obj_type;
|
||||||
|
$this->template->obj_type = $obj_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($filter) === 0) {
|
||||||
|
$this->template->logs = $this->searchResults($this->logs, $this->template->count);
|
||||||
|
} else {
|
||||||
|
$this->template->logs = $this->logs->search($filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->template->object_types = (new Logs)->getTypes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
namespace openvk\Web\Presenters;
|
namespace openvk\Web\Presenters;
|
||||||
use openvk\Web\Models\Entities\{IP, User, PasswordReset, EmailVerification};
|
use openvk\Web\Models\Entities\{IP, User, PasswordReset, EmailVerification};
|
||||||
use openvk\Web\Models\Repositories\{IPs, Users, Restores, Verifications};
|
use openvk\Web\Models\Repositories\{IPs, Logs, Users, Restores, Verifications};
|
||||||
use openvk\Web\Models\Exceptions\InvalidUserNameException;
|
use openvk\Web\Models\Exceptions\InvalidUserNameException;
|
||||||
use openvk\Web\Util\Validator;
|
use openvk\Web\Util\Validator;
|
||||||
use Chandler\Session\Session;
|
use Chandler\Session\Session;
|
||||||
|
@ -110,7 +110,7 @@ final class AuthPresenter extends OpenVKPresenter
|
||||||
$this->flashFail("err", tr("failed_to_register"), tr("user_already_exists"));
|
$this->flashFail("err", tr("failed_to_register"), tr("user_already_exists"));
|
||||||
|
|
||||||
$user->setUser($chUser->getId());
|
$user->setUser($chUser->getId());
|
||||||
$user->save();
|
$user->save(false);
|
||||||
|
|
||||||
if(!is_null($referer)) {
|
if(!is_null($referer)) {
|
||||||
$user->toggleSubscription($referer);
|
$user->toggleSubscription($referer);
|
||||||
|
@ -130,7 +130,9 @@ final class AuthPresenter extends OpenVKPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->authenticator->authenticate($chUser->getId());
|
$this->authenticator->authenticate($chUser->getId());
|
||||||
|
(new Logs)->create($user->getId(), "profiles", "openvk\\Web\\Models\\Entities\\User", 0, $user, $user);
|
||||||
$this->redirect("/id" . $user->getId());
|
$this->redirect("/id" . $user->getId());
|
||||||
|
$user->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use Chandler\Security\Authenticator;
|
||||||
use Latte\Engine as TemplatingEngine;
|
use Latte\Engine as TemplatingEngine;
|
||||||
use openvk\Web\Models\Entities\IP;
|
use openvk\Web\Models\Entities\IP;
|
||||||
use openvk\Web\Themes\Themepacks;
|
use openvk\Web\Themes\Themepacks;
|
||||||
use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets};
|
use openvk\Web\Models\Repositories\{CurrentUser, IPs, Users, APITokens, Tickets};
|
||||||
use WhichBrowser;
|
use WhichBrowser;
|
||||||
|
|
||||||
abstract class OpenVKPresenter extends SimplePresenter
|
abstract class OpenVKPresenter extends SimplePresenter
|
||||||
|
@ -211,6 +211,7 @@ abstract class OpenVKPresenter extends SimplePresenter
|
||||||
$this->user->id = $this->user->identity->getId();
|
$this->user->id = $this->user->identity->getId();
|
||||||
$this->template->thisUser = $this->user->identity;
|
$this->template->thisUser = $this->user->identity;
|
||||||
$this->template->userTainted = $user->isTainted();
|
$this->template->userTainted = $user->isTainted();
|
||||||
|
CurrentUser::get($this->user->identity);
|
||||||
|
|
||||||
if($this->user->identity->isDeleted() && !$this->deactivationTolerant) {
|
if($this->user->identity->isDeleted() && !$this->deactivationTolerant) {
|
||||||
if($this->user->identity->isDeactivated()) {
|
if($this->user->identity->isDeactivated()) {
|
||||||
|
@ -255,7 +256,7 @@ abstract class OpenVKPresenter extends SimplePresenter
|
||||||
if($this->user->identity->onlineStatus() == 0 && !($this->user->identity->isDeleted() || $this->user->identity->isBanned())) {
|
if($this->user->identity->onlineStatus() == 0 && !($this->user->identity->isDeleted() || $this->user->identity->isBanned())) {
|
||||||
$this->user->identity->setOnline(time());
|
$this->user->identity->setOnline(time());
|
||||||
$this->user->identity->setClient_name(NULL);
|
$this->user->identity->setClient_name(NULL);
|
||||||
$this->user->identity->save();
|
$this->user->identity->save(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->template->ticketAnsweredCount = (new Tickets)->getTicketsCountByUserId($this->user->id, 1);
|
$this->template->ticketAnsweredCount = (new Tickets)->getTicketsCountByUserId($this->user->id, 1);
|
||||||
|
|
|
@ -124,6 +124,9 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/settings/tuning">{_admin_settings_tuning}</a>
|
<a href="/admin/settings/tuning">{_admin_settings_tuning}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/logs">Логи</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/settings/appearance">{_admin_settings_appearance}</a>
|
<a href="/admin/settings/appearance">{_admin_settings_appearance}</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
99
Web/Presenters/templates/Admin/Logs.xml
Normal file
99
Web/Presenters/templates/Admin/Logs.xml
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
{extends "@layout.xml"}
|
||||||
|
|
||||||
|
{block title}
|
||||||
|
Логи
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block heading}
|
||||||
|
Логи
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block content}
|
||||||
|
{var $logs = iterator_to_array($logs)}
|
||||||
|
{var $amount = sizeof($logs)}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
del, ins { text-decoration: none; color: #000; }
|
||||||
|
del { background: #fdd; }
|
||||||
|
ins { background: #dfd; }
|
||||||
|
</style>
|
||||||
|
<form class="aui">
|
||||||
|
<div>
|
||||||
|
<select class="select medium-field" type="number" id="type" name="type" placeholder="Тип изменения">
|
||||||
|
<option value="any" n:attr="selected => !$type">Любое</option>
|
||||||
|
<option value="0" n:attr="selected => $type === 0">Создание</option>
|
||||||
|
<option value="1" n:attr="selected => $type === 1">Редактирование</option>
|
||||||
|
<option value="2" n:attr="selected => $type === 2">Удаление</option>
|
||||||
|
<option value="3" n:attr="selected => $type === 3">Восстановление</option>
|
||||||
|
</select>
|
||||||
|
<input class="text medium-field" type="number" id="id" name="id" placeholder="ID записи" n:attr="value => $id"/>
|
||||||
|
<input class="text medium-field" type="number" id="uid" name="uid" placeholder="ID пользователя" n:attr="value => $user"/>
|
||||||
|
</div>
|
||||||
|
<div style="margin: 8px 0;" />
|
||||||
|
<div>
|
||||||
|
<select class="select medium-field" id="obj_type" name="obj_type" placeholder="Тип объекта">
|
||||||
|
<option value="any" n:attr="selected => !$obj_type">Любой</option>
|
||||||
|
<option n:foreach="$object_types as $type" n:attr="selected => $obj_type === $type">{$type}</option>
|
||||||
|
</select>
|
||||||
|
<input class="text medium-field" type="number" id="obj_id" name="obj_id" placeholder="ID объекта" n:attr="value => $obj_id"/>
|
||||||
|
<input type="submit" class="aui-button aui-button-primary medium-field" value="Поиск" style="width: 165px;"/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<table class="aui aui-table-list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Пользователь</th>
|
||||||
|
<th>Объект</th>
|
||||||
|
<th>Тип</th>
|
||||||
|
<th>Изменения</th>
|
||||||
|
<th>Время</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr n:foreach="$logs as $log">
|
||||||
|
<td>{$log->getId()}</td>
|
||||||
|
<td>
|
||||||
|
<span n:if="$log->getUser()->getAvatarURL()" class="aui-avatar aui-avatar-xsmall">
|
||||||
|
<span class="aui-avatar-inner">
|
||||||
|
<img src="{$log->getUser()->getAvatarURL()}" alt="{$log->getUser()->getCanonicalName()}" style="object-fit: cover;" role="presentation" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<a href="{$log->getUser()->getURL()}">{$log->getUser()->getCanonicalName()}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span n:if="$log->getObjectAvatar()" class="aui-avatar aui-avatar-xsmall">
|
||||||
|
<span class="aui-avatar-inner">
|
||||||
|
<img src="{$log->getObjectAvatar()}" alt="{$log->getObjectName()}" style="object-fit: cover;" role="presentation" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<a href="{$log->getObjectURL()}">{$log->getObjectName()}</a>
|
||||||
|
</td>
|
||||||
|
<td>{$log->getTypeNom()}</td>
|
||||||
|
<td>
|
||||||
|
{foreach $log->getChanges() as $change}
|
||||||
|
<div>
|
||||||
|
<b>{$change["field"]}</b>:
|
||||||
|
{if array_key_exists('diff', $change)}
|
||||||
|
{$change["diff"]|noescape}
|
||||||
|
{else}
|
||||||
|
<ins>{$change["old_value"]}</ins>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/foreach}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{$change["ts"]}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br/>
|
||||||
|
<div align="right">
|
||||||
|
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
|
||||||
|
|
||||||
|
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">«</a>
|
||||||
|
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">»</a>
|
||||||
|
</div>
|
||||||
|
{/block}
|
|
@ -124,6 +124,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{if $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
|
{if $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
|
||||||
<a href="/admin/clubs/id{$club->getId()}" id="profile_link">{_manage_group_action}</a>
|
<a href="/admin/clubs/id{$club->getId()}" id="profile_link">{_manage_group_action}</a>
|
||||||
|
<a href="/admin/logs?obj_id={$club->getId()}&obj_type=Club" class="profile_link">Последние действия</a>
|
||||||
{/if}
|
{/if}
|
||||||
{if $club->getSubscriptionStatus($thisUser) == false}
|
{if $club->getSubscriptionStatus($thisUser) == false}
|
||||||
<form action="/setSub/club" method="post">
|
<form action="/setSub/club" method="post">
|
||||||
|
|
|
@ -118,6 +118,9 @@
|
||||||
<a href="javascript:warnUser()" class="profile_link" style="width: 194px;">
|
<a href="javascript:warnUser()" class="profile_link" style="width: 194px;">
|
||||||
{_warn_user_action}
|
{_warn_user_action}
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/admin/logs?uid={$user->getId()}" class="profile_link" style="width: 194px;">
|
||||||
|
Последние действия
|
||||||
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{if $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
|
{if $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
|
||||||
|
|
|
@ -49,3 +49,4 @@ services:
|
||||||
- openvk\Web\Models\Repositories\BannedLinks
|
- openvk\Web\Models\Repositories\BannedLinks
|
||||||
- openvk\Web\Models\Repositories\ChandlerGroups
|
- openvk\Web\Models\Repositories\ChandlerGroups
|
||||||
- openvk\Web\Presenters\MaintenancePresenter
|
- openvk\Web\Presenters\MaintenancePresenter
|
||||||
|
- openvk\Web\Models\Repositories\Logs
|
||||||
|
|
|
@ -339,6 +339,8 @@ routes:
|
||||||
handler: "Admin->chandlerGroup"
|
handler: "Admin->chandlerGroup"
|
||||||
- url: "/admin/chandler/users/{slug}"
|
- url: "/admin/chandler/users/{slug}"
|
||||||
handler: "Admin->chandlerUser"
|
handler: "Admin->chandlerUser"
|
||||||
|
- url: "/admin/logs"
|
||||||
|
handler: "Admin->logs"
|
||||||
- url: "/internal/wall{num}"
|
- url: "/internal/wall{num}"
|
||||||
handler: "Wall->wallEmbedded"
|
handler: "Wall->wallEmbedded"
|
||||||
- url: "/robots.txt"
|
- url: "/robots.txt"
|
||||||
|
|
19
install/00038-logs.sql
Normal file
19
install/00038-logs.sql
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
CREATE TABLE `logs`
|
||||||
|
(
|
||||||
|
`id` bigint(20) UNSIGNED NOT NULL,
|
||||||
|
`user` bigint(20) UNSIGNED NOT NULL,
|
||||||
|
`type` int(11) NOT NULL,
|
||||||
|
`object_table` tinytext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`object_model` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`object_id` bigint(20) UNSIGNED NOT NULL,
|
||||||
|
`xdiff_old` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`xdiff_new` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`ts` bigint(20) NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
ALTER TABLE `logs`
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
ALTER TABLE `logs`
|
||||||
|
MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;
|
||||||
|
COMMIT;
|
|
@ -102,6 +102,7 @@ openvk:
|
||||||
fartscroll: false
|
fartscroll: false
|
||||||
testLabel: false
|
testLabel: false
|
||||||
defaultMobileTheme: ""
|
defaultMobileTheme: ""
|
||||||
|
logs: true
|
||||||
|
|
||||||
telemetry:
|
telemetry:
|
||||||
plausible:
|
plausible:
|
||||||
|
|
Loading…
Reference in a new issue