mirror of
https://github.com/openvk/openvk
synced 2025-07-01 21:48:17 +03:00
noSpam
This commit is contained in:
parent
ecde2fd5d6
commit
e27cda1f08
12 changed files with 778 additions and 13 deletions
|
@ -361,6 +361,12 @@ class Club extends RowModel
|
||||||
$this->save();
|
$this->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unban(): void
|
||||||
|
{
|
||||||
|
$this->setBlock_Reason(null);
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
function getAlert(): ?string
|
function getAlert(): ?string
|
||||||
{
|
{
|
||||||
return $this->getRecord()->alert;
|
return $this->getRecord()->alert;
|
||||||
|
|
71
Web/Models/Entities/NoSpamLog.php
Normal file
71
Web/Models/Entities/NoSpamLog.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Models\Entities;
|
||||||
|
use openvk\Web\Models\RowModel;
|
||||||
|
use openvk\Web\Util\DateTime;
|
||||||
|
use openvk\Web\Models\Repositories\{Users};
|
||||||
|
use Nette\Database\Table\ActiveRow;
|
||||||
|
|
||||||
|
class NoSpamLog extends RowModel
|
||||||
|
{
|
||||||
|
protected $tableName = "noSpam_templates";
|
||||||
|
|
||||||
|
function getId(): int
|
||||||
|
{
|
||||||
|
return $this->getRecord()->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUser(): ?User
|
||||||
|
{
|
||||||
|
return (new Users)->get($this->getRecord()->user);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModel(): string
|
||||||
|
{
|
||||||
|
return $this->getRecord()->model;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRegex(): ?string
|
||||||
|
{
|
||||||
|
return $this->getRecord()->regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRequest(): ?string
|
||||||
|
{
|
||||||
|
return $this->getRecord()->request;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCount(): int
|
||||||
|
{
|
||||||
|
return $this->getRecord()->count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTime(): DateTime
|
||||||
|
{
|
||||||
|
return new DateTime($this->getRecord()->time);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getItems(): ?array
|
||||||
|
{
|
||||||
|
return explode(",", $this->getRecord()->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeRaw(): int
|
||||||
|
{
|
||||||
|
return $this->getRecord()->ban_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getType(): string
|
||||||
|
{
|
||||||
|
switch ($this->getTypeRaw()) {
|
||||||
|
case 1: return "О";
|
||||||
|
case 2: return "Б";
|
||||||
|
case 3: return "ОБ";
|
||||||
|
default: return (string) $this->getTypeRaw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRollbacked(): bool
|
||||||
|
{
|
||||||
|
return !is_null($this->getRecord()->rollback);
|
||||||
|
}
|
||||||
|
}
|
|
@ -264,22 +264,26 @@ class User extends RowModel
|
||||||
|
|
||||||
$reason = $ban->getReason();
|
$reason = $ban->getReason();
|
||||||
|
|
||||||
preg_match('/\*\*content-(post|photo|video|group|comment|note|app)-(\d+)\*\*$/', $reason, $matches);
|
preg_match('/\*\*content-(post|photo|video|group|comment|note|app|noSpamTemplate)-(\d+)\*\*$/', $reason, $matches);
|
||||||
if (sizeof($matches) === 3) {
|
if (sizeof($matches) === 3) {
|
||||||
if ($for !== "banned") {
|
$content_type = $matches[1]; $content_id = (int) $matches[2];
|
||||||
|
if ($content_type === "noSpamTemplate") {
|
||||||
$reason = "Подозрительная активность";
|
$reason = "Подозрительная активность";
|
||||||
} else {
|
} else {
|
||||||
$content_type = $matches[1]; $content_id = (int) $matches[2];
|
if ($for !== "banned") {
|
||||||
$reason = [$this->getTextForContentBan($content_type), $content_type];
|
$reason = "Подозрительная активность";
|
||||||
switch ($content_type) {
|
} else {
|
||||||
case "post": $reason[] = (new Posts)->get($content_id); break;
|
$reason = [$this->getTextForContentBan($content_type), $content_type];
|
||||||
case "photo": $reason[] = (new Photos)->get($content_id); break;
|
switch ($content_type) {
|
||||||
case "video": $reason[] = (new Videos)->get($content_id); break;
|
case "post": $reason[] = (new Posts)->get($content_id); break;
|
||||||
case "group": $reason[] = (new Clubs)->get($content_id); break;
|
case "photo": $reason[] = (new Photos)->get($content_id); break;
|
||||||
case "comment": $reason[] = (new Comments)->get($content_id); break;
|
case "video": $reason[] = (new Videos)->get($content_id); break;
|
||||||
case "note": $reason[] = (new Notes)->get($content_id); break;
|
case "group": $reason[] = (new Clubs)->get($content_id); break;
|
||||||
case "app": $reason[] = (new Applications)->get($content_id); break;
|
case "comment": $reason[] = (new Comments)->get($content_id); break;
|
||||||
default: $reason[] = null;
|
case "note": $reason[] = (new Notes)->get($content_id); break;
|
||||||
|
case "app": $reason[] = (new Applications)->get($content_id); break;
|
||||||
|
default: $reason[] = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -902,6 +906,21 @@ class User extends RowModel
|
||||||
$this->save();
|
$this->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unban(int $removed_by): void
|
||||||
|
{
|
||||||
|
$ban = (new Bans)->get((int) $this->getRawBanReason());
|
||||||
|
if (!$ban || $ban->isOver())
|
||||||
|
return;
|
||||||
|
|
||||||
|
$ban->setRemoved_Manually(true);
|
||||||
|
$ban->setRemoved_By($removed_by);
|
||||||
|
$ban->save();
|
||||||
|
|
||||||
|
$this->setBlock_Reason(NULL);
|
||||||
|
// $user->setUnblock_time(NULL);
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
function deactivate(?string $reason): void
|
function deactivate(?string $reason): void
|
||||||
{
|
{
|
||||||
$this->setDeleted(1);
|
$this->setDeleted(1);
|
||||||
|
|
34
Web/Models/Repositories/NoSpamLogs.php
Normal file
34
Web/Models/Repositories/NoSpamLogs.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Models\Repositories;
|
||||||
|
use Chandler\Database\DatabaseConnection;
|
||||||
|
use openvk\Web\Models\Entities\NoSpamLog;
|
||||||
|
use openvk\Web\Models\Entities\User;
|
||||||
|
use Nette\Database\Table\ActiveRow;
|
||||||
|
|
||||||
|
class NoSpamLogs
|
||||||
|
{
|
||||||
|
private $context;
|
||||||
|
private $noSpamLogs;
|
||||||
|
|
||||||
|
function __construct()
|
||||||
|
{
|
||||||
|
$this->context = DatabaseConnection::i()->getContext();
|
||||||
|
$this->noSpamLogs = $this->context->table("noSpam_templates");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toNoSpamLog(?ActiveRow $ar): ?NoSpamLog
|
||||||
|
{
|
||||||
|
return is_null($ar) ? NULL : new NoSpamLog($ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(int $id): ?NoSpamLog
|
||||||
|
{
|
||||||
|
return $this->toNoSpamLog($this->noSpamLogs->get($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getList(array $filter = []): \Traversable
|
||||||
|
{
|
||||||
|
foreach ($this->noSpamLogs->where($filter)->order("`id` DESC") as $log)
|
||||||
|
yield new NoSpamLog($log);
|
||||||
|
}
|
||||||
|
}
|
276
Web/Presenters/NoSpamPresenter.php
Normal file
276
Web/Presenters/NoSpamPresenter.php
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Presenters;
|
||||||
|
use Nette\Database\DriverException;
|
||||||
|
use Nette\Utils\Finder;
|
||||||
|
use Chandler\Database\DatabaseConnection;
|
||||||
|
use openvk\Web\Models\Entities\Club;
|
||||||
|
use openvk\Web\Models\Entities\Comment;
|
||||||
|
use openvk\Web\Models\Entities\NoSpamLog;
|
||||||
|
use openvk\Web\Models\Entities\User;
|
||||||
|
use openvk\Web\Models\Repositories\NoSpamLogs;
|
||||||
|
|
||||||
|
final class NoSpamPresenter extends OpenVKPresenter
|
||||||
|
{
|
||||||
|
protected $banTolerant = true;
|
||||||
|
protected $deactivationTolerant = true;
|
||||||
|
protected $presenterName = "nospam";
|
||||||
|
|
||||||
|
const ENTITIES_NAMESPACE = "openvk\\Web\\Models\\Entities";
|
||||||
|
|
||||||
|
function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderIndex(): void
|
||||||
|
{
|
||||||
|
$this->assertUserLoggedIn();
|
||||||
|
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
|
||||||
|
|
||||||
|
$targetDir = __DIR__ . '/../Models/Entities/';
|
||||||
|
$mode = in_array($this->queryParam("act"), ["form", "templates", "rollback"]) ? $this->queryParam("act") : "form";
|
||||||
|
|
||||||
|
if ($mode === "form") {
|
||||||
|
$this->template->_template = "NoSpam/Index";
|
||||||
|
$foundClasses = [];
|
||||||
|
foreach (Finder::findFiles('*.php')->from($targetDir) as $file) {
|
||||||
|
$content = file_get_contents($file->getPathname());
|
||||||
|
$namespacePattern = '/namespace\s+([^\s;]+)/';
|
||||||
|
$classPattern = '/class\s+([^\s{]+)/';
|
||||||
|
preg_match($namespacePattern, $content, $namespaceMatches);
|
||||||
|
preg_match($classPattern, $content, $classMatches);
|
||||||
|
|
||||||
|
if (isset($namespaceMatches[1]) && isset($classMatches[1])) {
|
||||||
|
$classNamespace = trim($namespaceMatches[1]);
|
||||||
|
$className = trim($classMatches[1]);
|
||||||
|
$fullClassName = $classNamespace . '\\' . $className;
|
||||||
|
|
||||||
|
if ($classNamespace === NoSpamPresenter::ENTITIES_NAMESPACE && class_exists($fullClassName)) {
|
||||||
|
$foundClasses[] = $className;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$models = [];
|
||||||
|
|
||||||
|
foreach ($foundClasses as $class) {
|
||||||
|
$r = new \ReflectionClass(NoSpamPresenter::ENTITIES_NAMESPACE . "\\$class");
|
||||||
|
if (!$r->isAbstract() && $r->getName() !== NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence")
|
||||||
|
$models[] = $class;
|
||||||
|
}
|
||||||
|
$this->template->models = $models;
|
||||||
|
} else if ($mode === "templates") {
|
||||||
|
$this->template->_template = "NoSpam/Templates.xml";
|
||||||
|
$filter = [];
|
||||||
|
if ($this->queryParam("id")) {
|
||||||
|
$filter["id"] = (int) $this->queryParam("id");
|
||||||
|
}
|
||||||
|
$this->template->templates = iterator_to_array((new NoSpamLogs)->getList($filter));
|
||||||
|
} else {
|
||||||
|
$template = (new NoSpamLogs)->get((int) $this->postParam("id"));
|
||||||
|
if (!$template || $template->isRollbacked())
|
||||||
|
$this->returnJson(["success" => false, "error" => "Шаблон не найден"]);
|
||||||
|
|
||||||
|
$model = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $template->getModel();
|
||||||
|
$items = $template->getItems();
|
||||||
|
if (count($items) > 0) {
|
||||||
|
$db = DatabaseConnection::i()->getContext();
|
||||||
|
|
||||||
|
$unbanned_ids = [];
|
||||||
|
foreach ($items as $_item) {
|
||||||
|
try {
|
||||||
|
$item = new $model;
|
||||||
|
$table_name = $item->getTableName();
|
||||||
|
$item = $db->table($table_name)->get((int) $_item);
|
||||||
|
if (!$item) continue;
|
||||||
|
|
||||||
|
$item = new $model($item);
|
||||||
|
|
||||||
|
if (key_exists("deleted", $item->unwrap()) && $item->isDeleted()) {
|
||||||
|
$item->setDeleted(0);
|
||||||
|
$item->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($template->getTypeRaw(), [2, 3])) {
|
||||||
|
$owner = NULL;
|
||||||
|
$methods = ["getOwner", "getUser", "getRecipient", "getInitiator"];
|
||||||
|
|
||||||
|
if (method_exists($item, "ban")) {
|
||||||
|
$owner = $item;
|
||||||
|
} else {
|
||||||
|
foreach ($methods as $method) {
|
||||||
|
if (method_exists($item, $method)) {
|
||||||
|
$owner = $item->$method();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId());
|
||||||
|
|
||||||
|
if (!in_array($_id, $unbanned_ids)) {
|
||||||
|
$owner->unban($this->user->id);
|
||||||
|
$unbanned_ids[] = $_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->returnJson(["success" => false, "error" => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->returnJson(["success" => false, "error" => "Объекты не найдены"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$template->setRollback(true);
|
||||||
|
$template->save();
|
||||||
|
|
||||||
|
$this->returnJson(["success" => true]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSearch(): void
|
||||||
|
{
|
||||||
|
$this->assertUserLoggedIn();
|
||||||
|
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
|
||||||
|
$this->assertNoCSRF();
|
||||||
|
$this->willExecuteWriteAction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$where = $this->postParam("where");
|
||||||
|
$ip = $this->postParam("ip");
|
||||||
|
$useragent = $this->postParam("useragent");
|
||||||
|
$searchTerm = $this->postParam("q");
|
||||||
|
$ts = $this->postParam("ts");
|
||||||
|
$te = $this->postParam("te");
|
||||||
|
|
||||||
|
if (!$ip && !$useragent && !$searchTerm && !$ts && !$te && !$where && !$searchTerm)
|
||||||
|
$this->returnJson(["success" => false, "error" => "Нет запроса. Заполните поле \"подстрока\" или введите запрос \"WHERE\" в поле под ним."]);
|
||||||
|
|
||||||
|
$model_name = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $this->postParam("model");
|
||||||
|
if (!class_exists($model_name))
|
||||||
|
$this->returnJson(["success" => false, "error" => "Модель не найдена"]);
|
||||||
|
|
||||||
|
$model = new $model_name;
|
||||||
|
|
||||||
|
$c = new \ReflectionClass($model_name);
|
||||||
|
if ($c->isAbstract() || $c->getName() == NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence")
|
||||||
|
$this->returnJson(["success" => false, "error" => "No."]);
|
||||||
|
|
||||||
|
$db = DatabaseConnection::i()->getContext();
|
||||||
|
$table = $model->getTableName();
|
||||||
|
$columns = $db->getStructure()->getColumns($table);
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
if (!$where) {
|
||||||
|
$conditions = [];
|
||||||
|
$need_deleted = false;
|
||||||
|
foreach ($columns as $column) {
|
||||||
|
if ($column["name"] == "deleted") {
|
||||||
|
$need_deleted = true;
|
||||||
|
} else {
|
||||||
|
$conditions[] = "`$column[name]` REGEXP '$searchTerm'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$where = "(" . implode(" OR ", $conditions) . ")";
|
||||||
|
if ($need_deleted) $where .= " AND `deleted` = 0";
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $db->query("SELECT * FROM `$table` WHERE $where");
|
||||||
|
$rows = $result->fetchAll();
|
||||||
|
|
||||||
|
if (!in_array((int)$this->postParam("ban"), [1, 2, 3])) {
|
||||||
|
$response = [];
|
||||||
|
foreach ($rows as $key => $object) {
|
||||||
|
$object = (array)$object;
|
||||||
|
$_obj = [];
|
||||||
|
foreach ($object as $key => $value) {
|
||||||
|
foreach ($columns as $column) {
|
||||||
|
if ($column["name"] === $key && in_array(strtoupper($column["nativetype"]), ["BLOB", "BINARY", "VARBINARY", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB"])) {
|
||||||
|
$value = "[BINARY]";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$_obj[$key] = $value;
|
||||||
|
}
|
||||||
|
$response[] = $_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->returnJson(["success" => true, "count" => count($response), "list" => $response]);
|
||||||
|
} else {
|
||||||
|
$ids = [];
|
||||||
|
|
||||||
|
foreach ($rows as $object) {
|
||||||
|
$object = new $model_name($db->table($table)->get($object->id));
|
||||||
|
if (!$object) continue;
|
||||||
|
$ids[] = $object->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
$log = new NoSpamLog;
|
||||||
|
$log->setUser($this->user->id);
|
||||||
|
$log->setModel($this->postParam("model"));
|
||||||
|
if ($searchTerm) {
|
||||||
|
$log->setRegex($searchTerm);
|
||||||
|
} else {
|
||||||
|
$log->setRequest($where);
|
||||||
|
}
|
||||||
|
$log->setBan_Type((int) $this->postParam("ban"));
|
||||||
|
$log->setCount(count($rows));
|
||||||
|
$log->setTime(time());
|
||||||
|
$log->setItems(implode(",", $ids));
|
||||||
|
$log->save();
|
||||||
|
|
||||||
|
$banned_ids = [];
|
||||||
|
foreach ($rows as $object) {
|
||||||
|
$object = new $model_name($db->table($table)->get($object->id));
|
||||||
|
if (!$object) continue;
|
||||||
|
|
||||||
|
$owner = NULL;
|
||||||
|
$methods = ["getOwner", "getUser", "getRecipient", "getInitiator"];
|
||||||
|
|
||||||
|
if (method_exists($object, "ban")) {
|
||||||
|
$owner = $object;
|
||||||
|
} else {
|
||||||
|
foreach ($methods as $method) {
|
||||||
|
if (method_exists($object, $method)) {
|
||||||
|
$owner = $object->$method();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($owner instanceof User && $owner->getId() === $this->user->id) {
|
||||||
|
if (count($rows) === 1) {
|
||||||
|
$this->returnJson(["success" => false, "error" => "\"Производственная травма\" — Вы не можете блокировать или удалять свой же контент"]);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array((int)$this->postParam("ban"), [2, 3])) {
|
||||||
|
if ($owner) {
|
||||||
|
$_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId());
|
||||||
|
if (!in_array($_id, $banned_ids)) {
|
||||||
|
if ($owner instanceof User) {
|
||||||
|
$owner->ban("**content-noSpamTemplate-" . $log->getId() . "**", false, time() + $owner->getNewBanTime(), $this->user->id);
|
||||||
|
} else {
|
||||||
|
$owner->ban("Подозрительная активность");
|
||||||
|
}
|
||||||
|
|
||||||
|
$banned_ids[] = $_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array((int)$this->postParam("ban"), [1, 3]))
|
||||||
|
$object->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->returnJson(["success" => true]);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->returnJson(["success" => false, "error" => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -214,6 +214,9 @@
|
||||||
(<b>{$reportNotAnsweredCount}</b>)
|
(<b>{$reportNotAnsweredCount}</b>)
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
|
<a n:if="$canAccessHelpdesk" href="/noSpam" class="link">
|
||||||
|
noSpam
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem"
|
n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem"
|
||||||
href="{$menuItem['url']}"
|
href="{$menuItem['url']}"
|
||||||
|
|
193
Web/Presenters/templates/NoSpam/Index.xml
Normal file
193
Web/Presenters/templates/NoSpam/Index.xml
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
{extends "../@layout.xml"}
|
||||||
|
|
||||||
|
{block title}noSpam{/block}
|
||||||
|
{block header}{include title}{/block}
|
||||||
|
|
||||||
|
{block content}
|
||||||
|
{include "Tabs.xml", mode => "form"}
|
||||||
|
<br />
|
||||||
|
<div style="display: flex; border: 1px solid #ECECEC; padding: 8px;">
|
||||||
|
<div id="noSpam-form" style="width: 50%; border-right: 1px solid #ECECEC;">
|
||||||
|
<table cellspacing="7" cellpadding="0" width="100%" border="0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td width="83px">
|
||||||
|
<span class="nobold">Раздел:</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select name="model" id="model" style="margin-left: -2px;">
|
||||||
|
<option selected>Не выбрано</option>
|
||||||
|
<option n:foreach="$models as $model" value="{$model}">
|
||||||
|
{$model}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div style="border-top: 1px solid #ECECEC; margin: 8px 0;" />
|
||||||
|
<table cellspacing="7" cellpadding="0" width="100%" border="0">
|
||||||
|
<tbody>
|
||||||
|
<tr style="width: 129px; border-top: 1px solid #ECECEC;">
|
||||||
|
<td>
|
||||||
|
<span class="nobold">Подстрока:</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="regex" placeholder="Regex" id="regex">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{* "Это могли бы быть мы с тобой, но" в овк нет логов :( *}
|
||||||
|
{*<tr style="width: 129px">
|
||||||
|
<td>
|
||||||
|
<span class="nobold">IP:</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="ip" placeholder="или подсеть">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="width: 129px">
|
||||||
|
<td>
|
||||||
|
<span class="nobold">Юзер-агент:</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="useragent" placeholder="Mozila 1.0 Blablabla/test">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="width: 129px">
|
||||||
|
<td>
|
||||||
|
<span class="nobold">Время раньше, чем:</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="date" name="ds">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="width: 129px">
|
||||||
|
<td>
|
||||||
|
<span class="nobold">Время позже, чем:</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="date" name="de">
|
||||||
|
</td>
|
||||||
|
</tr>*}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<center><b>ИЛИ</b></center>
|
||||||
|
<br />
|
||||||
|
<textarea style="resize: vertical; width: calc(100% - 6px)" placeholder='city = "Воскресенск" && id = 1' name="where" id="where" />
|
||||||
|
<div style="border-top: 1px solid #ECECEC; margin: 8px 0;" />
|
||||||
|
<table cellspacing="7" cellpadding="0" width="100%" border="0">
|
||||||
|
<tbody>
|
||||||
|
<tr style="width: 129px; border-top: 1px solid #ECECEC;">
|
||||||
|
<td>
|
||||||
|
<span class="nobold">Параметры блокировки:</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select name="ban_type" id="noSpam-ban-type">
|
||||||
|
<option value="1">Только откат</option>
|
||||||
|
<option value="2">Только блокировка</option>
|
||||||
|
<option value="3">Откат и блокировка</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div style="border-top: 1px solid #ECECEC; margin: 8px 0;" />
|
||||||
|
<center>
|
||||||
|
<div id="noSpam-buttons">
|
||||||
|
<input id="search" type="submit" value="Поиск" class="button" />
|
||||||
|
<input id="apply" type="submit" value="Применить" class="button" style="display: none;" />
|
||||||
|
</div>
|
||||||
|
<div id="noSpam-loader" style="display: none;">
|
||||||
|
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px;">
|
||||||
|
</div>
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
<div style="width: 50%;">
|
||||||
|
<center id="noSpam-results-loader" style="display: none;">
|
||||||
|
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px; margin: 125px 0;">
|
||||||
|
</center>
|
||||||
|
<center id="noSpam-results-text" style="margin: 125px 25px;">Здесь будут отображаться результаты поиска</center>
|
||||||
|
<div id="noSpam-results-block" style="display: none;">
|
||||||
|
<h4 style="padding: 8px;">Результаты поиска (<span id="noSpam-results-count" style="color: inherit; font-weight: inherit;"></span> шт.)</h4>
|
||||||
|
<ul style="padding-inline-start:18px;" id="noSpam-results-list"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
async function search(ban = false) {
|
||||||
|
$("#noSpam-results-text").hide();
|
||||||
|
$("#noSpam-results-block").hide();
|
||||||
|
$("#apply").hide();
|
||||||
|
$("#noSpam-buttons").hide();
|
||||||
|
|
||||||
|
$("#noSpam-results-loader").show();
|
||||||
|
$("#noSpam-loader").show();
|
||||||
|
|
||||||
|
let model = $("#model").val();
|
||||||
|
let regex = $("#regex").val();
|
||||||
|
let where = $("#where").val();
|
||||||
|
|
||||||
|
await $.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "/al_abuse/search",
|
||||||
|
data: {
|
||||||
|
model: model,
|
||||||
|
q: regex,
|
||||||
|
where: where,
|
||||||
|
ban: ban,
|
||||||
|
hash: {=$csrfToken}
|
||||||
|
},
|
||||||
|
success: (response) => {
|
||||||
|
if (response.success) {
|
||||||
|
console.log(response);
|
||||||
|
if (response.count > 0) {
|
||||||
|
$("#noSpam-results-list").empty();
|
||||||
|
$("#noSpam-results-count").text(response.count);
|
||||||
|
response.list.forEach((item) => {
|
||||||
|
const HTML_TAGS_REGEX = /<\/?([^>]+)(>|$)/g;
|
||||||
|
let fields = "";
|
||||||
|
Object.entries(item).map(([key, value]) => {
|
||||||
|
fields += `<b>${ key}</b>: ${ value?.toString()?.replace(HTML_TAGS_REGEX, "[$1]")}<br />`;
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#noSpam-results-list").append(`<li>
|
||||||
|
<a style="display: block;" onClick="$('#noSpam-result-fields-${ model}-${ item.id}').toggle()">
|
||||||
|
<h4 style="display: inherit; padding: 8px;">${ model} #${ item.id}</h4>
|
||||||
|
</a>
|
||||||
|
<div style="display: none;" id="noSpam-result-fields-${ model}-${ item.id}">${ fields}</div>
|
||||||
|
</li>`);
|
||||||
|
});
|
||||||
|
$("#noSpam-results-block").show();
|
||||||
|
$("#apply").show();
|
||||||
|
} else {
|
||||||
|
$("#noSpam-results-text").text(ban ? "Операция завершена успешно" : "Ничего не найдено :(");
|
||||||
|
$("#noSpam-results-text").show();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$("#noSpam-results-text").text(response?.error ?? "Неизвестная ошибка");
|
||||||
|
$("#noSpam-results-text").show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error("Error while searching noSpam:", error);
|
||||||
|
$("#noSpam-results-text").text("Ошибка при выполнении запроса");
|
||||||
|
$("#noSpam-results-text").show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#noSpam-buttons").show();
|
||||||
|
$("#noSpam-loader").hide();
|
||||||
|
$("#noSpam-results-loader").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#search").on("click", () => { search(); });
|
||||||
|
$("#regex, #where").keypress((e) => {
|
||||||
|
if (e.which === 13 && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#apply").on("click", () => { search(Number($("#noSpam-ban-type").val())); })
|
||||||
|
</script>
|
||||||
|
{/block}
|
8
Web/Presenters/templates/NoSpam/Tabs.xml
Normal file
8
Web/Presenters/templates/NoSpam/Tabs.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="tabs">
|
||||||
|
<div n:attr="id => ($mode === 'form' ? 'activetabs' : 'ki')" class="tab">
|
||||||
|
<a n:attr="id => ($mode === 'form' ? 'act_tab_a' : 'ki')" href="/noSpam">Бан по шаблону</a>
|
||||||
|
</div>
|
||||||
|
<div n:attr="id => ($mode === 'templates' ? 'activetabs' : 'ki')" class="tab">
|
||||||
|
<a n:attr="id => ($mode === 'templates' ? 'act_tab_a' : 'ki')" href="/noSpam?act=templates">Шаблоны</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
129
Web/Presenters/templates/NoSpam/Templates.xml
Normal file
129
Web/Presenters/templates/NoSpam/Templates.xml
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
{extends "../@layout.xml"}
|
||||||
|
|
||||||
|
{block title}Шаблоны{/block}
|
||||||
|
{block header}{include title}{/block}
|
||||||
|
|
||||||
|
{block content}
|
||||||
|
{include "Tabs.xml", mode => "templates"}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
table, th, td {
|
||||||
|
border: 1px solid #ECECEC;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
font-family: -apple-system, system-ui, "Helvetica Neue", Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(odd) {
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover, th:hover {
|
||||||
|
background-color: #E8EBEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.846em;
|
||||||
|
color: #626d7a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<table n:if="count($templates) > 0" cellspacing="0" cellpadding="7" width="100%">
|
||||||
|
<tr>
|
||||||
|
<th style="text-align: center;">ID</th>
|
||||||
|
<th>Пользователь</th>
|
||||||
|
<th style="text-align: center;">Раздел</th>
|
||||||
|
<th>Подстрока</th>
|
||||||
|
<th>Where</th>
|
||||||
|
<th style="text-align: center;">Тип</th>
|
||||||
|
<th style="text-align: center;">Количество</th>
|
||||||
|
<th>Время</th>
|
||||||
|
<th style="text-align: center;">Действия</th>
|
||||||
|
</tr>
|
||||||
|
<tr n:foreach="$templates as $template">
|
||||||
|
<td id="id-{$template->getId()}" onClick="openTableField('id', {$template->getId()})" style="text-align: center;"><b>{$template->getId()}</b></td>
|
||||||
|
<td id="user-{$template->getId()}" onClick="openTableField('user', {$template->getId()})">
|
||||||
|
<a href="{$template->getUser()->getURL()}" target="_blank">{$template->getUser()->getCanonicalName()}</a>
|
||||||
|
</td>
|
||||||
|
<td id="model-{$template->getId()}" onClick="openTableField('model', {$template->getId()})" style="text-align: center;">{$template->getModel()}</td>
|
||||||
|
<td id="regex-{$template->getId()}" onClick="openTableField('regex', {$template->getId()})">
|
||||||
|
<a>{$template->getRegex() ?? "-"}</a>
|
||||||
|
</td>
|
||||||
|
<td id="where-{$template->getId()}" onClick="openTableField('where', {$template->getId()})">
|
||||||
|
<a>{$template->getRequest() ?? "-"}</a>
|
||||||
|
</td>
|
||||||
|
<td id="type-{$template->getId()}" onClick="openTableField('type', {$template->getId()})" style="text-align: center;">{$template->getType()}</td>
|
||||||
|
<td id="count-{$template->getId()}" onClick="openTableField('count', {$template->getId()})" style="text-align: center;">
|
||||||
|
{$template->getCount()}
|
||||||
|
</td>
|
||||||
|
<td id="time-{$template->getId()}" onClick="openTableField('time', {$template->getId()})">{$template->getTime()}</td>
|
||||||
|
<td style="text-align: center;">
|
||||||
|
<div id="noSpam-rollback-{$template->getId()}">
|
||||||
|
<div id="noSpam-rollback-loader-{$template->getId()}" style="display: none;">
|
||||||
|
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px;">
|
||||||
|
</div>
|
||||||
|
<a n:if="!$template->isRollbacked()" id="noSpam-rollback-template-link-{$template->getId()}" onClick="rollbackTemplate({$template->getId()})">откатить</a>
|
||||||
|
<span n:attr="style => $template->isRollbacked() ? '' : 'display: none;'" id="noSpam-rollback-template-rollbacked-{$template->getId()}">откачен</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div n:if="count($templates) <= 0">
|
||||||
|
{include "../components/nothing.xml"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// Full width block
|
||||||
|
$(".navigation").hide();
|
||||||
|
$(".page_content").width("100%");
|
||||||
|
$(".page_body").width("100%").css("margin-right", 0).css("margin-top", "-5px");
|
||||||
|
$(".tabs").width("100%");
|
||||||
|
|
||||||
|
function openTableField(name, id) {
|
||||||
|
MessageBox(name, $(`#${ name}-${ id}`).text(), ["OK"], [Function.noop]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rollbackTemplate(id) {
|
||||||
|
$(`#noSpam-rollback-template-link-${ id}`).hide();
|
||||||
|
$(`#noSpam-rollback-template-rollbacked-${ id}`).hide();
|
||||||
|
$(`#noSpam-rollback-loader-${ id}`).show();
|
||||||
|
|
||||||
|
await $.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "/noSpam?act=rollback",
|
||||||
|
data: {
|
||||||
|
id: id,
|
||||||
|
hash: {=$csrfToken}
|
||||||
|
},
|
||||||
|
success: (response) => {
|
||||||
|
$(`#noSpam-rollback-loader-${ id}`).hide();
|
||||||
|
if (response.success) {
|
||||||
|
$(`#noSpam-rollback-template-rollbacked-${ id}`).show();
|
||||||
|
} else {
|
||||||
|
NewNotification("Ошибка", (response?.error ?? "Неизвестная ошибка"), "/assets/packages/static/openvk/img/error.png");
|
||||||
|
$(`#noSpam-rollback-template-link-${ id}`).show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
NewNotification("Ошибка", "Ошибка при отправке запроса", "/assets/packages/static/openvk/img/error.png");
|
||||||
|
$(`#noSpam-rollback-loader-${ id}`).hide();
|
||||||
|
$(`#noSpam-rollback-template-link-${ id}`).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{/block}
|
|
@ -51,3 +51,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\Presenters\NoSpamPresenter
|
||||||
|
|
|
@ -353,6 +353,10 @@ routes:
|
||||||
handler: "Admin->chandlerGroup"
|
handler: "Admin->chandlerGroup"
|
||||||
- url: "/admin/chandler/users/{slug}"
|
- url: "/admin/chandler/users/{slug}"
|
||||||
handler: "Admin->chandlerUser"
|
handler: "Admin->chandlerUser"
|
||||||
|
- url: "/noSpam"
|
||||||
|
handler: "NoSpam->index"
|
||||||
|
- url: "/al_abuse/search"
|
||||||
|
handler: "NoSpam->search"
|
||||||
- url: "/internal/wall{num}"
|
- url: "/internal/wall{num}"
|
||||||
handler: "Wall->wallEmbedded"
|
handler: "Wall->wallEmbedded"
|
||||||
- url: "/robots.txt"
|
- url: "/robots.txt"
|
||||||
|
|
21
install/sqls/00038-noSpam-templates.sql
Normal file
21
install/sqls/00038-noSpam-templates.sql
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
CREATE TABLE `noSpam_templates`
|
||||||
|
(
|
||||||
|
`id` bigint(20) UNSIGNED NOT NULL,
|
||||||
|
`user` bigint(20) UNSIGNED NOT NULL,
|
||||||
|
`model` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`regex` longtext COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
|
`request` longtext COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
|
`ban_type` tinyint(4) NOT NULL,
|
||||||
|
`count` bigint(20) NOT NULL,
|
||||||
|
`time` bigint(20) UNSIGNED NOT NULL,
|
||||||
|
`items` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`rollback` tinyint(1) DEFAULT NULL
|
||||||
|
) ENGINE = InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4
|
||||||
|
COLLATE = utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
ALTER TABLE `noSpam_templates`
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
ALTER TABLE `noSpam_templates`
|
||||||
|
MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;
|
Loading…
Reference in a new issue