diff --git a/Web/Models/Entities/Application.php b/Web/Models/Entities/Application.php
index 74569485..48975645 100644
--- a/Web/Models/Entities/Application.php
+++ b/Web/Models/Entities/Application.php
@@ -306,11 +306,14 @@ class Application extends RowModel
function delete(bool $softly = true): void
{
if($softly)
- throw new \UnexpectedValueException("Can't delete apps softly.");
+ throw new \UnexpectedValueException("Can't delete apps softly."); // why
$cx = DatabaseConnection::i()->getContext();
$cx->table("app_users")->where("app", $this->getId())->delete();
parent::delete(false);
}
+
+ function getPublicationTime(): string
+ { return tr("recently"); }
}
\ No newline at end of file
diff --git a/Web/Models/Entities/Ban.php b/Web/Models/Entities/Ban.php
new file mode 100644
index 00000000..3962c6cb
--- /dev/null
+++ b/Web/Models/Entities/Ban.php
@@ -0,0 +1,66 @@
+getRecord()->id;
+ }
+
+ function getReason(): ?string
+ {
+ return $this->getRecord()->reason;
+ }
+
+ function getUser(): ?User
+ {
+ return (new Users)->get($this->getRecord()->user);
+ }
+
+ function getInitiator(): ?User
+ {
+ return (new Users)->get($this->getRecord()->initiator);
+ }
+
+ function getStartTime(): int
+ {
+ return $this->getRecord()->iat;
+ }
+
+ function getEndTime(): int
+ {
+ return $this->getRecord()->exp;
+ }
+
+ function getTime(): int
+ {
+ return $this->getRecord()->time;
+ }
+
+ function isPermanent(): bool
+ {
+ return $this->getEndTime() === 0;
+ }
+
+ function isRemovedManually(): bool
+ {
+ return (bool) $this->getRecord()->removed_manually;
+ }
+
+ function isOver(): bool
+ {
+ return $this->isRemovedManually();
+ }
+
+ function whoRemoved(): ?User
+ {
+ return (new Users)->get($this->getRecord()->removed_by);
+ }
+}
diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php
index 06b2f49b..31485129 100644
--- a/Web/Models/Entities/Club.php
+++ b/Web/Models/Entities/Club.php
@@ -351,9 +351,21 @@ class Club extends RowModel
}
function getWebsite(): ?string
- {
- return $this->getRecord()->website;
- }
+ {
+ return $this->getRecord()->website;
+ }
+
+ function ban(string $reason): void
+ {
+ $this->setBlock_Reason($reason);
+ $this->save();
+ }
+
+ function unban(): void
+ {
+ $this->setBlock_Reason(null);
+ $this->save();
+ }
function getAlert(): ?string
{
diff --git a/Web/Models/Entities/NoSpamLog.php b/Web/Models/Entities/NoSpamLog.php
new file mode 100644
index 00000000..48d723c9
--- /dev/null
+++ b/Web/Models/Entities/NoSpamLog.php
@@ -0,0 +1,71 @@
+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);
+ }
+}
diff --git a/Web/Models/Entities/Postable.php b/Web/Models/Entities/Postable.php
index b743793f..23981cc1 100644
--- a/Web/Models/Entities/Postable.php
+++ b/Web/Models/Entities/Postable.php
@@ -34,7 +34,8 @@ abstract class Postable extends Attachable
$oid = (int) $this->getRecord()->owner;
if(!$real && $this->isAnonymous())
$oid = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"];
-
+
+ $oid = abs($oid);
if($oid > 0)
return (new Users)->get($oid);
else
diff --git a/Web/Models/Entities/Report.php b/Web/Models/Entities/Report.php
new file mode 100644
index 00000000..4070952e
--- /dev/null
+++ b/Web/Models/Entities/Report.php
@@ -0,0 +1,142 @@
+getRecord()->id;
+ }
+
+ function getStatus(): int
+ {
+ return $this->getRecord()->status;
+ }
+
+ function getContentType(): string
+ {
+ return $this->getRecord()->type;
+ }
+
+ function getReason(): string
+ {
+ return $this->getRecord()->reason;
+ }
+
+ function getTime(): DateTime
+ {
+ return new DateTime($this->getRecord()->date);
+ }
+
+ function isDeleted(): bool
+ {
+ if ($this->getRecord()->deleted === 0)
+ {
+ return false;
+ } elseif ($this->getRecord()->deleted === 1) {
+ return true;
+ }
+ }
+
+ function authorId(): int
+ {
+ return $this->getRecord()->user_id;
+ }
+
+ function getUser(): User
+ {
+ return (new Users)->get((int) $this->getRecord()->user_id);
+ }
+
+ function getContentId(): int
+ {
+ return (int) $this->getRecord()->target_id;
+ }
+
+ function getContentObject()
+ {
+ if ($this->getContentType() == "post") return (new Posts)->get($this->getContentId());
+ else if ($this->getContentType() == "photo") return (new Photos)->get($this->getContentId());
+ else if ($this->getContentType() == "video") return (new Videos)->get($this->getContentId());
+ else if ($this->getContentType() == "group") return (new Clubs)->get($this->getContentId());
+ else if ($this->getContentType() == "comment") return (new Comments)->get($this->getContentId());
+ else if ($this->getContentType() == "note") return (new Notes)->get($this->getContentId());
+ else if ($this->getContentType() == "app") return (new Applications)->get($this->getContentId());
+ else if ($this->getContentType() == "user") return (new Users)->get($this->getContentId());
+ else return null;
+ }
+
+ function getAuthor(): RowModel
+ {
+ return (new Posts)->get($this->getContentId())->getOwner();
+ }
+
+ function getReportAuthor(): User
+ {
+ return (new Users)->get($this->getRecord()->user_id);
+ }
+
+ function banUser($initiator)
+ {
+ $reason = $this->getContentType() !== "user" ? ("**content-" . $this->getContentType() . "-" . $this->getContentId() . "**") : ("Подозрительная активность");
+ $this->getAuthor()->ban($reason, false, time() + $this->getAuthor()->getNewBanTime(), $initiator);
+ }
+
+ function deleteContent()
+ {
+ if ($this->getContentType() !== "user") {
+ $pubTime = $this->getContentObject()->getPublicationTime();
+ $name = $this->getContentObject()->getName();
+ $this->getAuthor()->adminNotify("Ваш контент, который вы опубликовали $pubTime ($name) был удалён модераторами инстанса. За повторные или серьёзные нарушения вас могут заблокировать.");
+ $this->getContentObject()->delete($this->getContentType() !== "app");
+ }
+
+ $this->delete();
+ }
+
+ function getDuplicates(): \Traversable
+ {
+ return (new Reports)->getDuplicates($this->getContentType(), $this->getContentId(), $this->getId());
+ }
+
+ function getDuplicatesCount(): int
+ {
+ return count(iterator_to_array($this->getDuplicates()));
+ }
+
+ function hasDuplicates(): bool
+ {
+ return $this->getDuplicatesCount() > 0;
+ }
+
+ function getContentName(): string
+ {
+ if (method_exists($this->getContentObject(), "getCanonicalName"))
+ return $this->getContentObject()->getCanonicalName();
+
+ return $this->getContentType() . " #" . $this->getContentId();
+ }
+
+ public function delete(bool $softly = true): void
+ {
+ if ($this->hasDuplicates()) {
+ foreach ($this->getDuplicates() as $duplicate) {
+ $duplicate->setDeleted(1);
+ $duplicate->save();
+ }
+ }
+
+ $this->setDeleted(1);
+ $this->save();
+ }
+}
diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php
index db5cf055..e5b8da06 100644
--- a/Web/Models/Entities/User.php
+++ b/Web/Models/Entities/User.php
@@ -5,7 +5,7 @@ use openvk\Web\Themes\{Themepack, Themepacks};
use openvk\Web\Util\DateTime;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\{Photo, Message, Correspondence, Gift};
-use openvk\Web\Models\Repositories\{Photos, Users, Clubs, Albums, Gifts, Notifications};
+use openvk\Web\Models\Repositories\{Applications, Bans, Comments, Notes, Posts, Users, Clubs, Albums, Gifts, Notifications, Videos, Photos};
use openvk\Web\Models\Exceptions\InvalidUserNameException;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
@@ -241,11 +241,60 @@ class User extends RowModel
return $this->getRecord()->alert;
}
- function getBanReason(): ?string
+ function getTextForContentBan(string $type): string
+ {
+ switch ($type) {
+ case "post": return "за размещение от Вашего лица таких записей :";
+ case "photo": return "за размещение от Вашего лица таких фотографий :";
+ case "video": return "за размещение от Вашего лица таких видеозаписей :";
+ case "group": return "за подозрительное вступление от Вашего лица в группу: ";
+ case "comment": return "за размещение от Вашего лица таких комментариев :";
+ case "note": return "за размещение от Вашего лица таких заметок :";
+ case "app": return "за создание от Вашего имени подозрительных приложений .";
+ default: return "за размещение от Вашего лица такого контента :";
+ }
+ }
+
+ function getRawBanReason(): ?string
{
return $this->getRecord()->block_reason;
}
+ function getBanReason(?string $for = null)
+ {
+ $ban = (new Bans)->get((int) $this->getRecord()->block_reason);
+ if (!$ban || $ban->isOver()) return null;
+
+ $reason = $ban->getReason();
+
+ preg_match('/\*\*content-(post|photo|video|group|comment|note|app|noSpamTemplate|user)-(\d+)\*\*$/', $reason, $matches);
+ if (sizeof($matches) === 3) {
+ $content_type = $matches[1]; $content_id = (int) $matches[2];
+ if (in_array($content_type, ["noSpamTemplate", "user"])) {
+ $reason = "Подозрительная активность";
+ } else {
+ if ($for !== "banned") {
+ $reason = "Подозрительная активность";
+ } else {
+ $reason = [$this->getTextForContentBan($content_type), $content_type];
+ switch ($content_type) {
+ case "post": $reason[] = (new Posts)->get($content_id); break;
+ case "photo": $reason[] = (new Photos)->get($content_id); break;
+ case "video": $reason[] = (new Videos)->get($content_id); break;
+ case "group": $reason[] = (new Clubs)->get($content_id); break;
+ case "comment": $reason[] = (new Comments)->get($content_id); break;
+ case "note": $reason[] = (new Notes)->get($content_id); break;
+ case "app": $reason[] = (new Applications)->get($content_id); break;
+ case "user": $reason[] = (new Users)->get($content_id); break;
+ default: $reason[] = null;
+ }
+ }
+ }
+ }
+
+ return $reason;
+ }
+
function getBanInSupportReason(): ?string
{
return $this->getRecord()->block_in_support_reason;
@@ -833,7 +882,7 @@ class User extends RowModel
]);
}
- function ban(string $reason, bool $deleteSubscriptions = true, ?int $unban_time = NULL): void
+ function ban(string $reason, bool $deleteSubscriptions = true, $unban_time = NULL, ?int $initiator = NULL): void
{
if($deleteSubscriptions) {
$subs = DatabaseConnection::i()->getContext()->table("subscriptions");
@@ -846,8 +895,33 @@ class User extends RowModel
$subs->delete();
}
- $this->setBlock_Reason($reason);
- $this->setUnblock_time($unban_time);
+ $iat = time();
+ $ban = new Ban;
+ $ban->setUser($this->getId());
+ $ban->setReason($reason);
+ $ban->setInitiator($initiator);
+ $ban->setIat($iat);
+ $ban->setExp($unban_time !== "permanent" ? $unban_time : 0);
+ $ban->setTime($unban_time === "permanent" ? 0 : ($unban_time ? ($unban_time - $iat) : 0));
+ $ban->save();
+
+ $this->setBlock_Reason($ban->getId());
+ // $this->setUnblock_time($unban_time);
+ $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();
}
@@ -1099,7 +1173,11 @@ class User extends RowModel
function getUnbanTime(): ?string
{
- return !is_null($this->getRecord()->unblock_time) ? date('d.m.Y', $this->getRecord()->unblock_time) : NULL;
+ $ban = (new Bans)->get((int) $this->getRecord()->block_reason);
+ if (!$ban || $ban->isOver() || $ban->isPermanent()) return null;
+ if ($this->canUnbanThemself()) return tr("today");
+
+ return date('d.m.Y', $ban->getEndTime());
}
function canUnbanThemself(): bool
@@ -1107,10 +1185,40 @@ class User extends RowModel
if (!$this->isBanned())
return false;
- if ($this->getRecord()->unblock_time > time() || $this->getRecord()->unblock_time == 0)
- return false;
+ $ban = (new Bans)->get((int) $this->getRecord()->block_reason);
+ if (!$ban || $ban->isOver() || $ban->isPermanent()) return false;
- return true;
+ return $ban->getEndTime() <= time() && !$ban->isPermanent();
+ }
+
+ function getNewBanTime()
+ {
+ $bans = iterator_to_array((new Bans)->getByUser($this->getid()));
+ if (!$bans || count($bans) === 0)
+ return 0;
+
+ $last_ban = end($bans);
+ if (!$last_ban) return 0;
+
+ if ($last_ban->isPermanent()) return "permanent";
+
+ $values = [0, 3600, 7200, 86400, 172800, 604800, 1209600, 3024000, 9072000];
+ $response = 0;
+ $i = 0;
+
+ foreach ($values as $value) {
+ $i++;
+ if ($last_ban->getTime() === 0 && $value === 0) continue;
+ if ($last_ban->getTime() < $value) {
+ $response = $value;
+ break;
+ } else if ($last_ban->getTime() >= $value) {
+ if ($i < count($values)) continue;
+ $response = "permanent";
+ break;
+ }
+ }
+ return $response;
}
function toVkApiStruct(): object
diff --git a/Web/Models/Repositories/Bans.php b/Web/Models/Repositories/Bans.php
new file mode 100644
index 00000000..7123459d
--- /dev/null
+++ b/Web/Models/Repositories/Bans.php
@@ -0,0 +1,33 @@
+context = DB::i()->getContext();
+ $this->bans = $this->context->table("bans");
+ }
+
+ function toBan(?ActiveRow $ar): ?Ban
+ {
+ return is_null($ar) ? NULL : new Ban($ar);
+ }
+
+ function get(int $id): ?Ban
+ {
+ return $this->toBan($this->bans->get($id));
+ }
+
+ function getByUser(int $user_id): \Traversable
+ {
+ foreach ($this->bans->where("user", $user_id) as $ban)
+ yield new Ban($ban);
+ }
+}
\ No newline at end of file
diff --git a/Web/Models/Repositories/ChandlerUsers.php b/Web/Models/Repositories/ChandlerUsers.php
index a827afac..510e5860 100644
--- a/Web/Models/Repositories/ChandlerUsers.php
+++ b/Web/Models/Repositories/ChandlerUsers.php
@@ -28,7 +28,8 @@ class ChandlerUsers
function getById(string $UUID): ?ChandlerUser
{
- return new ChandlerUser($this->users->where("id", $UUID)->fetch());
+ $user = $this->users->where("id", $UUID)->fetch();
+ return $user ? new ChandlerUser($user) : NULL;
}
function getList(int $page = 1): \Traversable
diff --git a/Web/Models/Repositories/NoSpamLogs.php b/Web/Models/Repositories/NoSpamLogs.php
new file mode 100644
index 00000000..f8dd4980
--- /dev/null
+++ b/Web/Models/Repositories/NoSpamLogs.php
@@ -0,0 +1,34 @@
+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);
+ }
+}
diff --git a/Web/Models/Repositories/Reports.php b/Web/Models/Repositories/Reports.php
new file mode 100644
index 00000000..edce8980
--- /dev/null
+++ b/Web/Models/Repositories/Reports.php
@@ -0,0 +1,67 @@
+context = DatabaseConnection::i()->getContext();
+ $this->reports = $this->context->table("reports");
+ }
+
+ private function toReport(?ActiveRow $ar): ?Report
+ {
+ return is_null($ar) ? NULL : new Report($ar);
+ }
+
+ function getReports(int $state = 0, int $page = 1, ?string $type = NULL, ?bool $pagination = true): \Traversable
+ {
+ $filter = ["deleted" => 0];
+ if ($type) $filter["type"] = $type;
+
+ $reports = $this->reports->where($filter)->order("created DESC")->group("target_id, type");
+ if ($pagination)
+ $reports = $reports->page($page, 15);
+
+ foreach($reports as $t)
+ yield new Report($t);
+ }
+
+ function getReportsCount(int $state = 0): int
+ {
+ return sizeof($this->reports->where(["deleted" => 0, "type" => $state])->group("target_id, type"));
+ }
+
+ function get(int $id): ?Report
+ {
+ return $this->toReport($this->reports->get($id));
+ }
+
+ function getByContentId(int $id): ?Report
+ {
+ $post = $this->reports->where(["deleted" => 0, "content_id" => $id])->fetch();
+
+ if($post)
+ return new Report($post);
+ else
+ return null;
+ }
+
+ function getDuplicates(string $type, int $target_id, ?int $orig = NULL, ?int $user_id = NULL): \Traversable
+ {
+ $filter = ["deleted" => 0, "type" => $type, "target_id" => $target_id];
+ if ($orig) $filter[] = "id != $orig";
+ if ($user_id) $filter["user_id"] = $user_id;
+
+ foreach ($this->reports->where($filter) as $report)
+ yield new Report($report);
+ }
+
+ use \Nette\SmartObject;
+}
diff --git a/Web/Models/Repositories/Users.php b/Web/Models/Repositories/Users.php
index 6c165aa3..de0d341d 100644
--- a/Web/Models/Repositories/Users.php
+++ b/Web/Models/Repositories/Users.php
@@ -44,9 +44,9 @@ class Users
return $alias->getUser();
}
- function getByChandlerUser(ChandlerUser $user): ?User
+ function getByChandlerUser(?ChandlerUser $user): ?User
{
- return $this->toUser($this->users->where("user", $user->getId())->fetch());
+ return $user ? $this->toUser($this->users->where("user", $user->getId())->fetch()) : NULL;
}
function find(string $query, array $pars = [], string $sort = "id DESC"): Util\EntityStream
diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php
index bba2ef31..f5c40bcc 100644
--- a/Web/Presenters/AdminPresenter.php
+++ b/Web/Presenters/AdminPresenter.php
@@ -360,13 +360,19 @@ final class AdminPresenter extends OpenVKPresenter
{
$this->assertNoCSRF();
+ if (str_contains($this->queryParam("reason"), "*"))
+ exit(json_encode([ "error" => "Incorrect reason" ]));
+
$unban_time = strtotime($this->queryParam("date")) ?: NULL;
$user = $this->users->get($id);
if(!$user)
exit(json_encode([ "error" => "User does not exist" ]));
-
- $user->ban($this->queryParam("reason"), true, $unban_time);
+
+ if ($this->queryParam("incr"))
+ $unban_time = time() + $user->getNewBanTime();
+
+ $user->ban($this->queryParam("reason"), true, $unban_time, $this->user->identity->getId());
exit(json_encode([ "success" => true, "reason" => $this->queryParam("reason") ]));
}
@@ -377,9 +383,17 @@ final class AdminPresenter extends OpenVKPresenter
$user = $this->users->get($id);
if(!$user)
exit(json_encode([ "error" => "User does not exist" ]));
-
+
+ $ban = (new Bans)->get((int)$user->getRawBanReason());
+ if (!$ban || $ban->isOver())
+ exit(json_encode([ "error" => "User is not banned" ]));
+
+ $ban->setRemoved_Manually(true);
+ $ban->setRemoved_By($this->user->identity->getId());
+ $ban->save();
+
$user->setBlock_Reason(NULL);
- $user->setUnblock_time(NULL);
+ // $user->setUnblock_time(NULL);
$user->save();
exit(json_encode([ "success" => true ]));
}
@@ -465,6 +479,14 @@ final class AdminPresenter extends OpenVKPresenter
$this->redirect("/admin/bannedLinks");
}
+ function renderBansHistory(int $user_id) :void
+ {
+ $user = (new Users)->get($user_id);
+ if (!$user) $this->notFound();
+
+ $this->template->bans = (new Bans)->getByUser($user_id);
+ }
+
function renderChandlerGroups(): void
{
$this->template->groups = (new ChandlerGroups)->getList();
diff --git a/Web/Presenters/AuthPresenter.php b/Web/Presenters/AuthPresenter.php
index f569dd63..23b55dc9 100644
--- a/Web/Presenters/AuthPresenter.php
+++ b/Web/Presenters/AuthPresenter.php
@@ -1,7 +1,7 @@
flashFail("err", tr("error"), tr("forbidden"));
$user = $this->users->get($this->user->id);
+ $ban = (new Bans)->get((int)$user->getRawBanReason());
+ if (!$ban || $ban->isOver() || $ban->isPermanent())
+ $this->flashFail("err", tr("error"), tr("forbidden"));
+
+ $ban->setRemoved_Manually(2);
+ $ban->setRemoved_By($this->user->identity->getId());
+ $ban->save();
$user->setBlock_Reason(NULL);
- $user->setUnblock_Time(NULL);
+ // $user->setUnblock_Time(NULL);
$user->save();
$this->flashFail("succ", tr("banned_unban_title"), tr("banned_unban_description"));
diff --git a/Web/Presenters/BlobPresenter.php b/Web/Presenters/BlobPresenter.php
index 970b8f19..7bb3e2be 100644
--- a/Web/Presenters/BlobPresenter.php
+++ b/Web/Presenters/BlobPresenter.php
@@ -3,6 +3,8 @@ namespace openvk\Web\Presenters;
final class BlobPresenter extends OpenVKPresenter
{
+ protected $banTolerant = true;
+
private function getDirName($dir): string
{
if(gettype($dir) === "integer") {
diff --git a/Web/Presenters/NoSpamPresenter.php b/Web/Presenters/NoSpamPresenter.php
new file mode 100644
index 00000000..1560ba63
--- /dev/null
+++ b/Web/Presenters/NoSpamPresenter.php
@@ -0,0 +1,377 @@
+assertUserLoggedIn();
+ $this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
+
+ $targetDir = __DIR__ . '/../Models/Entities/';
+ $mode = in_array($this->queryParam("act"), ["form", "templates", "rollback", "reports"]) ? $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 if ($mode === "reports") {
+ $this->redirect("/scumfeed");
+ } 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();
+
+ function searchByAdditionalParams(?string $table = NULL, ?string $where = NULL, ?string $ip = NULL, ?string $useragent = NULL, ?int $ts = NULL, ?int $te = NULL, $user = NULL)
+ {
+ $db = DatabaseConnection::i()->getContext();
+ if ($table && ($ip || $useragent || $ts || $te || $user)) {
+ $conditions = [];
+
+ if ($ip) $conditions[] = "`ip` REGEXP '$ip'";
+ if ($useragent) $conditions[] = "`useragent` REGEXP '$useragent'";
+ if ($ts) $conditions[] = "`ts` < $ts";
+ if ($te) $conditions[] = "`ts` > $te";
+ if ($user) {
+ $users = new Users;
+
+ $_user = $users->getByChandlerUser((new ChandlerUsers)->getById($user))
+ ?? $users->get((int)$user)
+ ?? $users->getByAddress($user)
+ ?? NULL;
+
+ if ($_user) {
+ $conditions[] = "`user` = '" . $_user->getChandlerGUID() . "'";
+ }
+ }
+
+ $whereStart = "WHERE `object_table` = '$table'";
+ if ($table === "profiles") {
+ $whereStart .= "AND `type` = 0";
+ }
+
+ $conditions = count($conditions) > 0 ? "AND (" . implode(" AND ", $conditions) . ")" : "";
+ $response = [];
+
+ if ($conditions) {
+ $logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`");
+
+ if (!$where) {
+ foreach ($logs as $log) {
+ $log = (new Logs)->get($log->id);
+ $response[] = $log->getObject()->unwrap();
+ }
+ } else {
+ foreach ($logs as $log) {
+ $log = (new Logs)->get($log->id);
+ $object = $log->getObject()->unwrap();
+
+ if (!$object) continue;
+ if (str_starts_with($where, " AND")) {
+ $where = substr_replace($where, "", 0, strlen(" AND"));
+ }
+
+ foreach ($db->query("SELECT * FROM `$table` WHERE $where")->fetchAll() as $o) {
+ if ($object->id === $o["id"]) {
+ $response[] = $object;
+ }
+ }
+ }
+ }
+ }
+
+ return $response;
+ }
+ }
+
+ try {
+ $response = [];
+ $processed = 0;
+
+ $where = $this->postParam("where");
+ $ip = $this->postParam("ip");
+ $useragent = $this->postParam("useragent");
+ $searchTerm = $this->postParam("q");
+ $ts = (int)$this->postParam("ts");
+ $te = (int)$this->postParam("te");
+ $user = $this->postParam("user");
+
+ if (!$ip && !$useragent && !$searchTerm && !$ts && !$te && !$where && !$searchTerm && !$user)
+ $this->returnJson(["success" => false, "error" => "Нет запроса. Заполните поле \"подстрока\" или введите запрос \"WHERE\" в поле под ним."]);
+
+ $models = explode(",", $this->postParam("models"));
+
+ foreach ($models as $_model) {
+ $model_name = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $_model;
+ if (!class_exists($model_name)) {
+ continue;
+ }
+
+ $model = new $model_name;
+
+ $c = new \ReflectionClass($model_name);
+ if ($c->isAbstract() || $c->getName() == NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence") {
+ continue;
+ }
+
+ $db = DatabaseConnection::i()->getContext();
+ $table = $model->getTableName();
+ $columns = $db->getStructure()->getColumns($table);
+
+ if ($searchTerm) {
+ $conditions = [];
+ $need_deleted = false;
+ foreach ($columns as $column) {
+ if ($column["name"] == "deleted") {
+ $need_deleted = true;
+ } else {
+ $conditions[] = "`$column[name]` REGEXP '$searchTerm'";
+ }
+ }
+ $conditions = implode(" OR ", $conditions);
+
+ $where = ($this->postParam("where") ? " AND ($conditions)" : "($conditions)");
+ if ($need_deleted) $where .= " AND (`deleted` = 0)";
+ }
+
+ $rows = [];
+ if ($ip || $useragent || $ts || $te || $user) {
+ $rows = searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user);
+ }
+
+ if (count($rows) === 0) {
+ if (!$searchTerm) {
+ if (str_starts_with($where, " AND")) {
+ if ($searchTerm && !$this->postParam("where")) {
+ $where = substr_replace($where, "", 0, strlen(" AND"));
+ } else {
+ $where = "(" . $this->postParam("where") . ")" . $where;
+ }
+ }
+
+ if (!$where) {
+ $rows = [];
+ } else {
+ $result = $db->query("SELECT * FROM `$table` WHERE $where");
+ $rows = $result->fetchAll();
+ }
+ }
+ }
+
+ if (!in_array((int)$this->postParam("ban"), [1, 2, 3])) {
+ 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;
+ $_obj["__model_name"] = $_model;
+ }
+ $response[] = $_obj;
+ }
+ } 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($_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();
+ }
+
+ $processed++;
+ }
+ }
+
+ $this->returnJson(["success" => true, "processed" => $processed, "count" => count($response), "list" => $response]);
+ } catch (\Throwable $e) {
+ $this->returnJson(["success" => false, "error" => $e->getMessage()]);
+ }
+ }
+}
diff --git a/Web/Presenters/OpenVKPresenter.php b/Web/Presenters/OpenVKPresenter.php
old mode 100755
new mode 100644
index b4444bda..80ab0621
--- 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\{CurrentUser, IPs, Users, APITokens, Tickets};
+use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets, Reports, CurrentUser};
use WhichBrowser;
abstract class OpenVKPresenter extends SimplePresenter
@@ -260,8 +260,10 @@ abstract class OpenVKPresenter extends SimplePresenter
}
$this->template->ticketAnsweredCount = (new Tickets)->getTicketsCountByUserId($this->user->id, 1);
- if($user->can("write")->model("openvk\Web\Models\Entities\TicketReply")->whichBelongsTo(0))
+ if($user->can("write")->model("openvk\Web\Models\Entities\TicketReply")->whichBelongsTo(0)) {
$this->template->helpdeskTicketNotAnsweredCount = (new Tickets)->getTicketCount(0);
+ $this->template->reportNotAnsweredCount = (new Reports)->getReportsCount(0);
+ }
}
header("X-OpenVK-User-Validated: $userValidated");
diff --git a/Web/Presenters/ReportPresenter.php b/Web/Presenters/ReportPresenter.php
new file mode 100644
index 00000000..68d27861
--- /dev/null
+++ b/Web/Presenters/ReportPresenter.php
@@ -0,0 +1,151 @@
+reports = $reports;
+
+ parent::__construct();
+ }
+
+ function renderList(): void
+ {
+ $this->assertUserLoggedIn();
+ $this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
+ if ($_SERVER["REQUEST_METHOD"] === "POST")
+ $this->assertNoCSRF();
+
+ $act = in_array($this->queryParam("act"), ["post", "photo", "video", "group", "comment", "note", "app", "user"]) ? $this->queryParam("act") : NULL;
+
+ if (!$this->queryParam("orig")) {
+ $this->template->reports = $this->reports->getReports(0, (int)($this->queryParam("p") ?? 1), $act, $_SERVER["REQUEST_METHOD"] !== "POST");
+ $this->template->count = $this->reports->getReportsCount();
+ } else {
+ $orig = $this->reports->get((int) $this->queryParam("orig"));
+ if (!$orig) $this->redirect("/scumfeed");
+
+ $this->template->reports = $orig->getDuplicates();
+ $this->template->count = $orig->getDuplicatesCount();
+ $this->template->orig = $orig->getId();
+ }
+ $this->template->paginatorConf = (object) [
+ "count" => $this->template->count,
+ "page" => $this->queryParam("p") ?? 1,
+ "amount" => NULL,
+ "perPage" => 15,
+ ];
+ $this->template->mode = $act ?? "all";
+
+ if ($_SERVER["REQUEST_METHOD"] === "POST") {
+ $reports = [];
+ foreach ($this->reports->getReports(0, 0, $act, false) as $report) {
+ $reports[] = [
+ "id" => $report->getId(),
+ "author" => [
+ "id" => $report->getReportAuthor()->getId(),
+ "url" => $report->getReportAuthor()->getURL(),
+ "name" => $report->getReportAuthor()->getCanonicalName(),
+ "is_female" => $report->getReportAuthor()->isFemale()
+ ],
+ "content" => [
+ "name" => $report->getContentName(),
+ "type" => $report->getContentType(),
+ "id" => $report->getContentId(),
+ "url" => $report->getContentType() === "user" ? (new Users)->get((int) $report->getContentId())->getURL() : NULL
+ ],
+ "duplicates" => $report->getDuplicatesCount(),
+ ];
+ }
+ $this->returnJson(["reports" => $reports]);
+ }
+ }
+
+ function renderView(int $id): void
+ {
+ $this->assertUserLoggedIn();
+ $this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
+
+ $report = $this->reports->get($id);
+ if(!$report || $report->isDeleted())
+ $this->notFound();
+
+ $this->template->report = $report;
+ }
+
+ function renderCreate(int $id): void
+ {
+ $this->assertUserLoggedIn();
+ $this->willExecuteWriteAction();
+
+ if(!$id)
+ exit(json_encode([ "error" => tr("error_segmentation") ]));
+
+ if(in_array($this->queryParam("type"), ["post", "photo", "video", "group", "comment", "note", "app", "user"])) {
+ if (count(iterator_to_array($this->reports->getDuplicates($this->queryParam("type"), $id, NULL, $this->user->id))) <= 0) {
+ $report = new Report;
+ $report->setUser_id($this->user->id);
+ $report->setTarget_id($id);
+ $report->setType($this->queryParam("type"));
+ $report->setReason($this->queryParam("reason"));
+ $report->setCreated(time());
+ $report->save();
+ }
+
+ exit(json_encode([ "reason" => $this->queryParam("reason") ]));
+ } else {
+ exit(json_encode([ "error" => "Unable to submit a report on this content type" ]));
+ }
+ }
+
+ function renderAction(int $id): void
+ {
+ $this->assertUserLoggedIn();
+ $this->willExecuteWriteAction();
+ $this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
+
+ $report = $this->reports->get($id);
+ if(!$report || $report->isDeleted()) $this->notFound();
+
+ if ($this->postParam("ban")) {
+ $report->deleteContent();
+ $report->banUser($this->user->identity->getId());
+
+ $this->flash("suc", "Смэрть...", "Пользователь успешно забанен.");
+ } else if ($this->postParam("delete")) {
+ $report->deleteContent();
+
+ $this->flash("suc", "Нехай живе!", "Контент удалён, а пользователю прилетело предупреждение.");
+ } else if ($this->postParam("ignore")) {
+ $report->delete();
+
+ $this->flash("suc", "Нехай живе!", "Жалоба проигнорирована.");
+ } else if ($this->postParam("banClubOwner") || $this->postParam("banClub")) {
+ if ($report->getContentType() !== "group")
+ $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
+
+ $club = $report->getContentObject();
+ if (!$club || $club->isBanned())
+ $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
+
+ if ($this->postParam("banClubOwner")) {
+ $club->getOwner()->ban("**content-" . $report->getContentType() . "-" . $report->getContentId() . "**", false, $club->getOwner()->getNewBanTime(), $this->user->identity->getId());
+ } else {
+ $club->ban("**content-" . $report->getContentType() . "-" . $report->getContentId() . "**");
+ }
+
+ $report->delete();
+
+ $this->flash("suc", "Смэрть...", ($this->postParam("banClubOwner") ? "Создатель сообщества успешно забанен." : "Сообщество успешно забанено"));
+ }
+
+ $this->redirect("/scumfeed");
+ }
+}
diff --git a/Web/Presenters/templates/@banned.xml b/Web/Presenters/templates/@banned.xml
index 48c29ddb..7640838c 100644
--- a/Web/Presenters/templates/@banned.xml
+++ b/Web/Presenters/templates/@banned.xml
@@ -10,8 +10,19 @@
- {tr("banned_1", htmlentities($thisUser->getCanonicalName()))|noescape}
- {tr("banned_2", htmlentities($thisUser->getBanReason()))|noescape}
+ {var $ban = $thisUser->getBanReason("banned")}
+ {if is_string($ban)}
+ {tr("banned_1", htmlentities($thisUser->getCanonicalName()))|noescape}
+ {tr("banned_2", htmlentities($thisUser->getBanReason()))|noescape}
+ {else}
+ {tr("banned_1", htmlentities($thisUser->getCanonicalName()))|noescape}
+
+ Эта страница была заморожена {$ban[0]|noescape}
+ {if $ban[1] !== "app"}
+ {include "Report/ViewContent.xml", type => $ban[1], object => $ban[2]}
+ {/if}
+
+ {/if}
{if !$thisUser->getUnbanTime()}
{_banned_perm}
diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml
index 1718c499..c79a8e89 100644
--- a/Web/Presenters/templates/@layout.xml
+++ b/Web/Presenters/templates/@layout.xml
@@ -209,6 +209,25 @@
({$helpdeskTicketNotAnsweredCount} )
{/if}
+ {tr("reports")}
+ {if $reportNotAnsweredCount > 0}
+ ({$reportNotAnsweredCount} )
+ {/if}
+
+
+ noSpam
+
+ {$menuItem["name"]}
+
-
+ {ifset $thisUser}
+ {if !$thisUser->isBanned()}
+
+ {/if}
+ {/ifset}
diff --git a/Web/Presenters/templates/@listView.xml b/Web/Presenters/templates/@listView.xml
index 34739b59..f2342a85 100644
--- a/Web/Presenters/templates/@listView.xml
+++ b/Web/Presenters/templates/@listView.xml
@@ -12,16 +12,24 @@
{include size, x => $dat}
{/ifset}
+ {ifset before_content}
+ {include before_content, x => $dat}
+ {/ifset}
+
{ifset specpage}
{include specpage, x => $dat}
{else}
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
+ {ifset top}
+ {include top, x => $dat}
+ {/ifset}
+
{if sizeof($data) > 0}
-
+
diff --git a/Web/Presenters/templates/Admin/BansHistory.xml b/Web/Presenters/templates/Admin/BansHistory.xml
new file mode 100644
index 00000000..c0dc1b64
--- /dev/null
+++ b/Web/Presenters/templates/Admin/BansHistory.xml
@@ -0,0 +1,86 @@
+{extends "./@layout.xml"}
+
+{block title}
+ История блокировок
+{/block}
+
+{block heading}
+ {include title}
+{/block}
+
+{block content}
+
+{/block}
\ No newline at end of file
diff --git a/Web/Presenters/templates/Apps/Play.xml b/Web/Presenters/templates/Apps/Play.xml
index 91cfe5d5..facaa273 100644
--- a/Web/Presenters/templates/Apps/Play.xml
+++ b/Web/Presenters/templates/Apps/Play.xml
@@ -1,4 +1,5 @@
{extends "../@layout.xml"}
+{var $canReport = $owner->getId() !== $thisUser->getId()}
{block title}
{$name}
@@ -6,6 +7,7 @@
{block header}
{$name}
+ Пожаловаться
{/block}
{block content}
@@ -33,5 +35,29 @@
window.appOrigin = {$origin};
+
+
{script "js/al_games.js"}
{/block}
diff --git a/Web/Presenters/templates/Group/View.xml b/Web/Presenters/templates/Group/View.xml
index 10d1f0dd..42cdbde3 100644
--- a/Web/Presenters/templates/Group/View.xml
+++ b/Web/Presenters/templates/Group/View.xml
@@ -141,6 +141,34 @@
{/if}
+ {var $canReport = $thisUser->getId() != $club->getOwner()->getId()}
+ {if $canReport}
+ {_report}
+
+
+ {/if}
diff --git a/Web/Presenters/templates/NoSpam/Index.xml b/Web/Presenters/templates/NoSpam/Index.xml
new file mode 100644
index 00000000..746f86d5
--- /dev/null
+++ b/Web/Presenters/templates/NoSpam/Index.xml
@@ -0,0 +1,315 @@
+{extends "../@layout.xml"}
+
+{block title}noSpam{/block}
+{block header}{include title}{/block}
+
+{block content}
+
+
{include "Tabs.xml", mode => "form"}
+
+
+
+
+
+
+
+
Здесь будут отображаться результаты поиска
+
+
Результаты поиска
+
+ ( шт.)
+
+
+
+
+
+
+
+{/block}
diff --git a/Web/Presenters/templates/NoSpam/Tabs.xml b/Web/Presenters/templates/NoSpam/Tabs.xml
new file mode 100644
index 00000000..e80db91e
--- /dev/null
+++ b/Web/Presenters/templates/NoSpam/Tabs.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/Web/Presenters/templates/NoSpam/Templates.xml b/Web/Presenters/templates/NoSpam/Templates.xml
new file mode 100644
index 00000000..83fd77c7
--- /dev/null
+++ b/Web/Presenters/templates/NoSpam/Templates.xml
@@ -0,0 +1,131 @@
+{extends "../@layout.xml"}
+
+{block title}Шаблоны{/block}
+{block header}{include title}{/block}
+
+{block content}
+
{include "Tabs.xml", mode => "templates"}
+
+
+
+
+
+
+ {include "../components/nothing.xml"}
+
+
+
+{/block}
diff --git a/Web/Presenters/templates/Photos/Photo.xml b/Web/Presenters/templates/Photos/Photo.xml
index ad9f9efd..047f382a 100644
--- a/Web/Presenters/templates/Photos/Photo.xml
+++ b/Web/Presenters/templates/Photos/Photo.xml
@@ -42,10 +42,38 @@
{_actions}
+ {if $thisUser->getId() != $photo->getOwner()->getId()}
+ {var canReport = true}
+ {/if}
+
{_"open_original"}
+
{_report}
+
{_open_original}
diff --git a/Web/Presenters/templates/Report/List.xml b/Web/Presenters/templates/Report/List.xml
new file mode 100644
index 00000000..3d6a527b
--- /dev/null
+++ b/Web/Presenters/templates/Report/List.xml
@@ -0,0 +1,60 @@
+{extends "../@listView.xml"}
+{var iterator = iterator_to_array($reports)}
+{var page = $paginatorConf->page}
+{var table_body_id = "reports"}
+
+{block tabs}{include "../NoSpam/Tabs.xml", mode => "reports"}{/block}
+{block before_content}
+ {include "./Tabs.xml", mode => $mode}
+{/block}
+
+{block title}{_list_of_reports}{/block}
+
+{block header}
+ {_list_of_reports}
+{/block}
+
+{block actions}
+
+{/block}
+
+{block top}
+ {if !is_null($orig)}
+ Дубликаты жалобы №{$orig}
+ {/if}
+{/block}
+
+{* BEGIN ELEMENTS DESCRIPTION *}
+
+{block link|strip|stripHtml}
+ /admin/report{$x->getId()}
+{/block}
+
+{block preview}
+
+{/block}
+
+{block name}
+ Жалоба №{$x->getId()}
+{/block}
+
+{block description}
+
+ {$x->getReportAuthor()->getCanonicalName()}
+
+ пожаловал{!$x->getReportAuthor()->isFemale() ? 'ся' : 'ась'} на
+ {if $x->getContentType() === "user"}{/if}
+ {$x->getContentName()}
+ {if $x->getContentType() === "user"} {/if}
+
+ {if $x->hasDuplicates() && !$orig}
+
+ Другие жалобы на этот контент: {$x->getDuplicatesCount()} шт.
+ {/if}
+{/block}
+
+{block bottom}
+
+
+
+{/block}
diff --git a/Web/Presenters/templates/Report/Tabs.xml b/Web/Presenters/templates/Report/Tabs.xml
new file mode 100644
index 00000000..e878ccb3
--- /dev/null
+++ b/Web/Presenters/templates/Report/Tabs.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Web/Presenters/templates/Report/View.xml b/Web/Presenters/templates/Report/View.xml
new file mode 100644
index 00000000..4a0682d1
--- /dev/null
+++ b/Web/Presenters/templates/Report/View.xml
@@ -0,0 +1,37 @@
+{extends "../@layout.xml"}
+
+{block title}{$report->getReason()}{/block}
+
+{block header}
+ {_list_of_reports}
+ »
+ {_report_number}{$report->getId()}
+{/block}
+
+{block content}
+ {include "../NoSpam/Tabs.xml", mode => "reports"}
+
+
+ {$report->getReportAuthor()->getCanonicalName()} пожаловался на {$report->getContentName()}
+
+ {_comment}: {$report->getReason()}
+
+ {include "ViewContent.xml", type => $report->getContentType(), object => $report->getContentObject()}
+
+
+{/block}
diff --git a/Web/Presenters/templates/Report/ViewContent.xml b/Web/Presenters/templates/Report/ViewContent.xml
new file mode 100644
index 00000000..3928495f
--- /dev/null
+++ b/Web/Presenters/templates/Report/ViewContent.xml
@@ -0,0 +1,30 @@
+{block ViewContent}
+
+ {if $type == "post"}
+ {include "../components/post/oldpost.xml",
+ post => $object,
+ forceNoDeleteLink => true,
+ forceNoPinLink => true,
+ forceNoCommentsLink => true,
+ forceNoShareLink => true,
+ forceNoLike => true
+ }
+ {elseif $type == "photo"}
+ {include "./content/photo.xml", photo => $object}
+ {elseif $type == "video"}
+ {include "./content/video.xml", video => $object}
+ {elseif $type == "group" || $type == "user"}
+ {include "../components/group.xml", group => $object, isUser => $type == "user"}
+ {elseif $type == "comment"}
+ {include "../components/comment.xml", comment => $object, timeOnly => true}
+ {elseif $type == "note"}
+ {include "./content/note.xml", note => $object}
+ {elseif $type == "app"}
+ {if $appsSoftDeleting}
+ {include "./content/app.xml", app => $object}
+ {/if}
+ {else}
+ {include "../components/error.xml", description => tr("version_incompatibility")}
+ {/if}
+
+{/block}
diff --git a/Web/Presenters/templates/Report/content/app.xml b/Web/Presenters/templates/Report/content/app.xml
new file mode 100644
index 00000000..cd82caf2
--- /dev/null
+++ b/Web/Presenters/templates/Report/content/app.xml
@@ -0,0 +1,22 @@
+{block content}
+
+{/block}
diff --git a/Web/Presenters/templates/Report/content/note.xml b/Web/Presenters/templates/Report/content/note.xml
new file mode 100644
index 00000000..f4f2e054
--- /dev/null
+++ b/Web/Presenters/templates/Report/content/note.xml
@@ -0,0 +1,18 @@
+{block content}
+
+
+
+ {$note->getText()|noescape}
+
+
+{/block}
diff --git a/Web/Presenters/templates/Report/content/photo.xml b/Web/Presenters/templates/Report/content/photo.xml
new file mode 100644
index 00000000..ac9f4c97
--- /dev/null
+++ b/Web/Presenters/templates/Report/content/photo.xml
@@ -0,0 +1,26 @@
+{block content}
+
+
+
+
+
+
+
+
+
+
+
+
{_information}
+
{_info_description}:
+ {$photo->getDescription() ?? "(" . tr("none") . ")"}
+
{_info_uploaded_by}:
+
{$photo->getOwner()->getFullName()}
+
{_info_upload_date}:
+ {$photo->getPublicationTime()}
+
+
+
+
+
+
+{/block}
diff --git a/Web/Presenters/templates/Report/content/video.xml b/Web/Presenters/templates/Report/content/video.xml
new file mode 100644
index 00000000..f5e07b28
--- /dev/null
+++ b/Web/Presenters/templates/Report/content/video.xml
@@ -0,0 +1,32 @@
+{block content}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {$video->getName()}
+
+
+
+
+ {$video->getDescription() ?? ""}
+
+ {_video_uploaded} {$video->getPublicationTime()}
+ {_video_updated} {$video->getEditTime() ?? $video->getPublicationTime()}
+
+
+
+
+
+{/block}
diff --git a/Web/Presenters/templates/Support/AnswerTicket.xml b/Web/Presenters/templates/Support/AnswerTicket.xml
index 7f577f9d..cf81045e 100644
--- a/Web/Presenters/templates/Support/AnswerTicket.xml
+++ b/Web/Presenters/templates/Support/AnswerTicket.xml
@@ -8,7 +8,42 @@
{block content}
{$ticket->getName()}
- {_author}:
{$ticket->getUser()->getFullName()} | {$ticket->getUser()->getRegistrationIP()} | {_status}: {$ticket->getStatus()}.
+ {_author}:
+
+ {$ticket->getUser()->getFullName()}
+ | {$ticket->getUser()->getRegistrationIP()}
+ | {_status}: {$ticket->getStatus()}.
+ |
Блокировка
+
+
Причина блокировки
+
Так пользователь видит экран с информацией о блокировке:
+
+ {var $ban = $ticket->getUser()->getBanReason("banned")}
+
+
+
+
+ {if is_string($ban)}
+ {tr("banned_1", htmlentities($ticket->getUser()->getCanonicalName()))|noescape}
+ {tr("banned_2", htmlentities($ban))|noescape}
+ {else}
+ {tr("banned_1", htmlentities($ticket->getUser()->getCanonicalName()))|noescape}
+
+ Эта страница была заморожена {$ban[0]|noescape}
+ {if $ban[1] !== "app"}
+ {include "../Report/ViewContent.xml", type => $ban[1], object => $ban[2]}
+ {/if}
+
+ {/if}
+
+ {if !$ticket->getUser()->getUnbanTime()}
+ {_banned_perm}
+ {else}
+ {tr("banned_until_time", $ticket->getUser()->getUnbanTime())|noescape}
+ {/if}
+
+
+
{$ticket->getText()|noescape}
diff --git a/Web/Presenters/templates/User/View.xml b/Web/Presenters/templates/User/View.xml
index bc197310..b3cd3257 100644
--- a/Web/Presenters/templates/User/View.xml
+++ b/Web/Presenters/templates/User/View.xml
@@ -118,6 +118,9 @@
{_warn_user_action}
+
+ Блокировки
+
Последние действия
@@ -166,6 +169,31 @@
{/if}
+
+
{_report}
+
{/if}
{tr("followers", $user->getFollowersCount())}
@@ -604,13 +632,15 @@
uBanMsgTxt += "Предупреждение : Это действие удалит все подписки пользователя и отпишет всех от него.";
uBanMsgTxt += "Причина бана : "
uBanMsgTxt += "Заблокировать до : ";
+ uBanMsgTxt += " Автоматически (до " + {date('d.m.Y H\h', time() + $user->getNewBanTime())} + ") ";
MessageBox("Забанить " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
(function() {
res = document.querySelector("#uBanMsgInput").value;
date = document.querySelector("#uBanMsgDate").value;
+ incr = document.querySelector("#uBanMsgIncr").checked ? '1' : '0';
xhr = new XMLHttpRequest();
- xhr.open("GET", "/admin/ban/" + {$user->getId()} + "?reason=" + res + "&date=" + date + "&hash=" + {rawurlencode($csrfToken)}, true);
+ xhr.open("GET", "/admin/ban/" + {$user->getId()} + "?reason=" + res + "&incr=" + incr + "&date=" + date + "&hash=" + {rawurlencode($csrfToken)}, true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("success") === -1)
MessageBox("Ошибка", "Не удалось забанить пользователя...", ["OK"], [Function.noop]);
diff --git a/Web/Presenters/templates/User/banned.xml b/Web/Presenters/templates/User/banned.xml
index a495a59d..8abe4d89 100644
--- a/Web/Presenters/templates/User/banned.xml
+++ b/Web/Presenters/templates/User/banned.xml
@@ -3,7 +3,9 @@
{tr("user_banned", htmlentities($user->getFirstName()))|noescape}
{_user_banned_comment} {$user->getBanReason()} .
- Пользователь заблокирован до: {$user->getUnbanTime()}
+ Пользователь заблокирован
+ до: {$user->getUnbanTime()}
+ навсегда
{if isset($thisUser)}
diff --git a/Web/Presenters/templates/Videos/View.xml b/Web/Presenters/templates/Videos/View.xml
index 529f63dc..7cc41fe4 100644
--- a/Web/Presenters/templates/Videos/View.xml
+++ b/Web/Presenters/templates/Videos/View.xml
@@ -59,6 +59,38 @@
{_delete}
+
+ {if isset($thisUser)}
+ {if $thisUser->getId() != $video->getOwner()->getId()}
+ {var canReport = true}
+ {/if}
+ {/if}
+
+ {_report}
+
+
{/block}
diff --git a/Web/Presenters/templates/Wall/Post.xml b/Web/Presenters/templates/Wall/Post.xml
index 28855d0d..575c7bba 100644
--- a/Web/Presenters/templates/Wall/Post.xml
+++ b/Web/Presenters/templates/Wall/Post.xml
@@ -28,8 +28,35 @@
{_actions}
{if isset($thisUser)}
{var $canDelete = $post->canBeDeletedBy($thisUser)}
+ {if $thisUser->getId() != $post->getOwner()->getId()}
+ {var $canReport = true}
+ {/if}
{/if}
{_delete}
+ {_report}
+
{/block}
diff --git a/Web/Presenters/templates/components/comment.xml b/Web/Presenters/templates/components/comment.xml
index 0016e4e5..ef7150ff 100644
--- a/Web/Presenters/templates/components/comment.xml
+++ b/Web/Presenters/templates/components/comment.xml
@@ -29,6 +29,24 @@
+
\ No newline at end of file
diff --git a/Web/Presenters/templates/components/group.xml b/Web/Presenters/templates/components/group.xml
new file mode 100644
index 00000000..0db45156
--- /dev/null
+++ b/Web/Presenters/templates/components/group.xml
@@ -0,0 +1,43 @@
+{block content}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{/block}
diff --git a/Web/Presenters/templates/components/photo.xml b/Web/Presenters/templates/components/photo.xml
new file mode 100644
index 00000000..05c1913d
--- /dev/null
+++ b/Web/Presenters/templates/components/photo.xml
@@ -0,0 +1,5 @@
+{block content}
+
+
+
+{/block}
diff --git a/Web/Presenters/templates/components/video.xml b/Web/Presenters/templates/components/video.xml
index 752191a1..33961c4f 100644
--- a/Web/Presenters/templates/components/video.xml
+++ b/Web/Presenters/templates/components/video.xml
@@ -1,35 +1,37 @@
+{block content}
\ No newline at end of file
+
+
+
+
+
+
+ {ifset infotable}
+ {include infotable, x => $dat}
+ {else}
+
+
+ {$video->getName()}
+
+
+
+
+ {$video->getDescription() ?? ""}
+
+ {_video_uploaded} {$video->getPublicationTime()}
+
+
+ {_view_video}
+ {if $video->getCommentsCount() > 0}| {_comments} ({$video->getCommentsCount()}) {/if}
+
+ {/ifset}
+
+
+
+ quickBanInSupport"
- url: "/admin/support/unban/{num}"
handler: "Support->quickUnbanInSupport"
+ - url: "/admin/support/reports"
+ handler: "Report->list"
+ - url: "/scumfeed"
+ handler: "Report->list"
+ - url: "/admin/report{num}"
+ handler: "Report->view"
+ - url: "/admin/report{num}"
+ handler: "Report->view"
+ - url: "/admin/reportAction{num}"
+ handler: "Report->action"
+ - url: "/report/{num}"
+ handler: "Report->create"
- url: "/admin/bannedLinks"
handler: "Admin->bannedLinks"
- url: "/admin/bannedLink/id{num}"
handler: "Admin->bannedLink"
- url: "/admin/bannedLink/id{num}/unban"
handler: "Admin->unbanLink"
+ - url: "/admin/user{num}/bans"
+ handler: "Admin->bansHistory"
- url: "/upload/photo/{text}"
handler: "VKAPI->photoUpload"
- url: "/method/{text}.{text}"
@@ -341,6 +355,10 @@ routes:
handler: "Admin->chandlerGroup"
- url: "/admin/chandler/users/{slug}"
handler: "Admin->chandlerUser"
+ - url: "/noSpam"
+ handler: "NoSpam->index"
+ - url: "/al_abuse/search"
+ handler: "NoSpam->search"
- url: "/admin/logs"
handler: "Admin->logs"
- url: "/internal/wall{num}"
diff --git a/Web/static/css/main.css b/Web/static/css/main.css
index bbbe4d28..c6d22a04 100644
--- a/Web/static/css/main.css
+++ b/Web/static/css/main.css
@@ -671,6 +671,7 @@ input[type~="phone"],
input[type="search"],
input[type~="search"],
input[type~="date"],
+input[type~="datetime-local"],
select {
border: 1px solid #C0CAD5;
padding: 3px;
diff --git a/Web/static/img/supp_icons.png b/Web/static/img/supp_icons.png
new file mode 100644
index 00000000..b630ba7f
Binary files /dev/null and b/Web/static/img/supp_icons.png differ
diff --git a/install/sqls/00018-reports.sql b/install/sqls/00018-reports.sql
new file mode 100644
index 00000000..2357e0fd
--- /dev/null
+++ b/install/sqls/00018-reports.sql
@@ -0,0 +1,11 @@
+CREATE TABLE IF NOT EXISTS `reports` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ `user_id` bigint(20) NOT NULL,
+ `target_id` bigint(20) NOT NULL,
+ `type` varchar(64) NOT NULL,
+ `reason` text NOT NULL,
+ `deleted` tinyint(1) NOT NULL DEFAULT '0',
+ `created` bigint(20) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+ALTER TABLE `reports` ADD INDEX (`id`);
diff --git a/install/sqls/00032-better-reports.sql b/install/sqls/00032-better-reports.sql
new file mode 100644
index 00000000..86de58e7
--- /dev/null
+++ b/install/sqls/00032-better-reports.sql
@@ -0,0 +1,19 @@
+CREATE TABLE `bans`
+(
+ `id` bigint(20) UNSIGNED NOT NULL,
+ `user` bigint(20) UNSIGNED NOT NULL,
+ `initiator` bigint(20) UNSIGNED NOT NULL,
+ `iat` bigint(20) UNSIGNED NOT NULL,
+ `exp` bigint(20) NOT NULL,
+ `time` bigint(20) NOT NULL,
+ `reason` text COLLATE utf8mb4_unicode_ci NOT NULL,
+ `removed_manually` tinyint(1) DEFAULT 0,
+ `removed_by` bigint(20) UNSIGNED NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+ALTER TABLE `bans`
+ ADD PRIMARY KEY (`id`);
+
+ALTER TABLE `bans`
+ MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;
+COMMIT;
diff --git a/install/sqls/00038-noSpam-templates.sql b/install/sqls/00038-noSpam-templates.sql
new file mode 100644
index 00000000..d18974b6
--- /dev/null
+++ b/install/sqls/00038-noSpam-templates.sql
@@ -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;
diff --git a/locales/en.strings b/locales/en.strings
index 2b71592a..487d2156 100644
--- a/locales/en.strings
+++ b/locales/en.strings
@@ -889,6 +889,8 @@
"support_new_title" = "Enter the topic of your ticket";
"support_new_content" = "Describe the issue or suggestion";
+"reports" = "Reports";
+
"support_rate_good_answer" = "This is good answer";
"support_rate_bad_answer" = "This is bad answer";
"support_good_answer_user" = "You left a positive feedback.";
@@ -901,6 +903,12 @@
"fast_answers" = "Fast answers";
+"ignore_report" = "Ignore report";
+"report_number" = "Report #";
+"list_of_reports" = "List of reports";
+"text_of_the_post" = "Text of the post";
+"today" = "today";
+
"comment" = "Comment";
"sender" = "Sender";
@@ -1285,6 +1293,8 @@
"url_is_banned_title" = "Link to a suspicious site";
"url_is_banned_proceed" = "Follow the link";
+"recently" = "Recently";
+
/* Helpdesk */
"helpdesk" = "Support";
"helpdesk_agent" = "Support Agent";
diff --git a/locales/ru.strings b/locales/ru.strings
index e42c2fd0..dc101e2b 100644
--- a/locales/ru.strings
+++ b/locales/ru.strings
@@ -823,6 +823,8 @@
"support_new" = "Новое обращение";
"support_new_title" = "Введите тему вашего обращения";
"support_new_content" = "Опишите проблему или предложение";
+
+
"support_rate_good_answer" = "Это хороший ответ";
"support_rate_bad_answer" = "Это плохой ответ";
"support_good_answer_user" = "Вы оставили положительный отзыв.";
@@ -833,6 +835,14 @@
"support_rated_bad" = "Вы оставили негативный отзыв об ответе.";
"wrong_parameters" = "Неверные параметры запроса.";
"fast_answers" = "Быстрые ответы";
+
+"reports" = "Жалобы";
+"ignore_report" = "Игнорировать жалобу";
+"report_number" = "Жалоба №";
+"list_of_reports" = "Список жалоб";
+"text_of_the_post" = "Текст записи";
+"today" = "сегодня";
+
"comment" = "Комментарий";
"sender" = "Отправитель";
"author" = "Автор";
@@ -1173,6 +1183,8 @@
"url_is_banned_title" = "Ссылка на подозрительный сайт";
"url_is_banned_proceed" = "Перейти по ссылке";
+"recently" = "Недавно";
+
/* Helpdesk */
"helpdesk" = "Поддержка";
"helpdesk_agent" = "Агент Поддержки";