diff --git a/DBEntity.updated.php b/DBEntity.updated.php new file mode 100644 index 00000000..4c039b54 --- /dev/null +++ b/DBEntity.updated.php @@ -0,0 +1,140 @@ +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(); + $user_id = is_null($user) ? (int)OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"] : $user->getUser()->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 = []; + } + + function getTableName(): string + { + return $this->getTable()->getName(); + } + + use \Nette\SmartObject; +} diff --git a/Web/Models/Entities/Comment.php b/Web/Models/Entities/Comment.php index 7c6222e4..d813d6be 100644 --- a/Web/Models/Entities/Comment.php +++ b/Web/Models/Entities/Comment.php @@ -85,4 +85,9 @@ class Comment extends Post } return $res; } + + function getURL(): string + { + return "/wall" . $this->getTarget()->getPrettyId() . "#_comment" . $this->getId(); + } } diff --git a/Web/Models/Entities/IP.php b/Web/Models/Entities/IP.php index ecea92ca..df2c9787 100644 --- a/Web/Models/Entities/IP.php +++ b/Web/Models/Entities/IP.php @@ -92,7 +92,7 @@ class IP extends RowModel $this->stateChanges("rate_limit_counter", $aCounter); $this->stateChanges("rate_limit_violation_counter_start", $vCounterSessionStart); $this->stateChanges("rate_limit_violation_counter", $vCounter); - $this->save(); + $this->save(false); } } @@ -105,11 +105,11 @@ class IP extends RowModel $this->stateChanges("ip", $ip); } - function save(): void + function save($log): void { if(is_null($this->getRecord())) $this->stateChanges("first_seen", time()); - parent::save(); + parent::save($log); } } diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index d132e015..db5cf055 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -1016,7 +1016,7 @@ class User extends RowModel { $this->setOnline(time()); $this->setClient_name($platform); - $this->save(); + $this->save(false); return true; } @@ -1034,7 +1034,7 @@ class User extends RowModel function adminNotify(string $message): bool { - $admId = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"]; + $admId = (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"]; if(!$admId) return false; else if(is_null($admin = (new Users)->get($admId))) diff --git a/Web/Models/Repositories/CurrentUser.php b/Web/Models/Repositories/CurrentUser.php new file mode 100644 index 00000000..c6cb942b --- /dev/null +++ b/Web/Models/Repositories/CurrentUser.php @@ -0,0 +1,49 @@ +user = $user; + + if ($ip) + $this->ip = $ip; + + if ($useragent) + $this->useragent = $useragent; + } + + public static function get($user, $ip, $useragent) + { + if (self::$instance === null) self::$instance = new self($user, $ip, $useragent); + return self::$instance; + } + + public function getUser(): User + { + return $this->user; + } + + public function getIP(): string + { + return $this->ip; + } + + public function getUserAgent(): string + { + return $this->useragent; + } + + public static function i() + { + return self::$instance; + } +} diff --git a/Web/Models/Repositories/IPs.php b/Web/Models/Repositories/IPs.php index c4485b73..59fc6570 100644 --- a/Web/Models/Repositories/IPs.php +++ b/Web/Models/Repositories/IPs.php @@ -24,7 +24,7 @@ class IPs if(!$res) { $res = new IP; $res->setIp($ip); - $res->save(); + $res->save(false); return $res; } diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php index ed1f55eb..bba2ef31 100644 --- a/Web/Presenters/AdminPresenter.php +++ b/Web/Presenters/AdminPresenter.php @@ -1,7 +1,9 @@ gifts = $gifts; $this->bannedLinks = $bannedLinks; $this->chandlerGroups = $chandlerGroups; + $this->logs = DatabaseConnection::i()->getContext()->table("ChandlerLogs"); parent::__construct(); } @@ -551,4 +555,38 @@ final class AdminPresenter extends OpenVKPresenter $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 = $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; + } + + $this->template->logs = (new Logs)->search($filter); + $this->template->object_types = (new Logs)->getTypes(); + } } diff --git a/Web/Presenters/AuthPresenter.php b/Web/Presenters/AuthPresenter.php index 2f178900..f569dd63 100644 --- a/Web/Presenters/AuthPresenter.php +++ b/Web/Presenters/AuthPresenter.php @@ -1,7 +1,7 @@ flashFail("err", tr("failed_to_register"), tr("user_already_exists")); $user->setUser($chUser->getId()); - $user->save(); + $user->save(false); if(!is_null($referer)) { $user->toggleSubscription($referer); @@ -130,7 +130,9 @@ final class AuthPresenter extends OpenVKPresenter } $this->authenticator->authenticate($chUser->getId()); + (new Logs)->create($user->getId(), "profiles", "openvk\\Web\\Models\\Entities\\User", 0, $user, $user, $_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"]); $this->redirect("/id" . $user->getId()); + $user->save(); } } diff --git a/Web/Presenters/OpenVKPresenter.php b/Web/Presenters/OpenVKPresenter.php index 710713e5..b4444bda 100755 --- a/Web/Presenters/OpenVKPresenter.php +++ b/Web/Presenters/OpenVKPresenter.php @@ -7,7 +7,7 @@ use Chandler\Security\Authenticator; use Latte\Engine as TemplatingEngine; use openvk\Web\Models\Entities\IP; 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; abstract class OpenVKPresenter extends SimplePresenter @@ -211,6 +211,7 @@ abstract class OpenVKPresenter extends SimplePresenter $this->user->id = $this->user->identity->getId(); $this->template->thisUser = $this->user->identity; $this->template->userTainted = $user->isTainted(); + CurrentUser::get($this->user->identity, $_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"]); if($this->user->identity->isDeleted() && !$this->deactivationTolerant) { 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())) { $this->user->identity->setOnline(time()); $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); diff --git a/Web/Presenters/templates/Admin/@layout.xml b/Web/Presenters/templates/Admin/@layout.xml index 7b1a30f3..f254dd9a 100644 --- a/Web/Presenters/templates/Admin/@layout.xml +++ b/Web/Presenters/templates/Admin/@layout.xml @@ -124,6 +124,9 @@
  • {_admin_settings_tuning}
  • +
  • + Логи +
  • {_admin_settings_appearance}
  • diff --git a/Web/Presenters/templates/Admin/Logs.xml b/Web/Presenters/templates/Admin/Logs.xml new file mode 100644 index 00000000..d953a378 --- /dev/null +++ b/Web/Presenters/templates/Admin/Logs.xml @@ -0,0 +1,92 @@ +{extends "@layout.xml"} + +{block title} + Логи +{/block} + +{block heading} + Логи +{/block} + +{block content} + {var $amount = sizeof($logs)} + + +
    +
    + + + +
    +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    IDПользовательОбъектТипИзмененияВремя
    {$log->getId()} + {$log->getUser()} + + + + {$log->getObjectName()} + + + {$log->getObjectName()} + {_$log->getTypeNom()} + {foreach $log->getChanges() as $change} +
    + {$change["field"]}: + {if array_key_exists('diff', $change)} + {$change["diff"]|noescape} + {else} + {$change["old_value"]} + {/if} +
    + {/foreach} +
    + {=new openvk\Web\Util\DateTime($change["ts"])} +
    +
    +
    + {var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count} + + « + » +
    +{/block} diff --git a/Web/Presenters/templates/Group/View.xml b/Web/Presenters/templates/Group/View.xml index bf392a9f..10d1f0dd 100644 --- a/Web/Presenters/templates/Group/View.xml +++ b/Web/Presenters/templates/Group/View.xml @@ -124,6 +124,7 @@ {/if} {if $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)} {_manage_group_action} + Последние действия {/if} {if $club->getSubscriptionStatus($thisUser) == false}
    diff --git a/Web/Presenters/templates/User/View.xml b/Web/Presenters/templates/User/View.xml index 1fa71026..bc197310 100644 --- a/Web/Presenters/templates/User/View.xml +++ b/Web/Presenters/templates/User/View.xml @@ -118,6 +118,9 @@ {_warn_user_action} + + Последние действия + {/if} {if $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)} diff --git a/Web/routes.yml b/Web/routes.yml index 267c0608..0ff60bb6 100644 --- a/Web/routes.yml +++ b/Web/routes.yml @@ -341,6 +341,8 @@ routes: handler: "Admin->chandlerGroup" - url: "/admin/chandler/users/{slug}" handler: "Admin->chandlerUser" + - url: "/admin/logs" + handler: "Admin->logs" - url: "/internal/wall{num}" handler: "Wall->wallEmbedded" - url: "/robots.txt" diff --git a/locales/en.strings b/locales/en.strings index ce4ac0bf..2b71592a 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -1210,6 +1210,15 @@ "admin_banned_link_not_specified" = "The link is not specified"; "admin_banned_link_not_found" = "Link not found"; +"logs_adding" = "Creation"; +"logs_editing" = "Editing"; +"logs_removing" = "Deletion"; +"logs_restoring" = "Restoring"; +"logs_added" = "created"; +"logs_edited" = "edited"; +"logs_removed" = "removed"; +"logs_restored" = "restored"; + /* Paginator (deprecated) */ "paginator_back" = "Back"; diff --git a/locales/ru.strings b/locales/ru.strings index d4b85c79..e42c2fd0 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -1096,6 +1096,14 @@ "admin_banned_link_initiator" = "Инициатор"; "admin_banned_link_not_specified" = "Ссылка не указана"; "admin_banned_link_not_found" = "Ссылка не найдена"; +"logs_adding" = "Создание"; +"logs_editing" = "Редактирование"; +"logs_removing" = "Удаление"; +"logs_restoring" = "Восстановление"; +"logs_added" = "добавил"; +"logs_edited" = "отредактировал"; +"logs_removed" = "удалил"; +"logs_restored" = "восстановил"; /* Paginator (deprecated) */ diff --git a/openvk-example.yml b/openvk-example.yml index e3fd1c3a..4b45e7ba 100644 --- a/openvk-example.yml +++ b/openvk-example.yml @@ -102,6 +102,7 @@ openvk: fartscroll: false testLabel: false defaultMobileTheme: "" + logs: true telemetry: plausible: