Reports enhancements

This commit is contained in:
n1rwana 2023-08-09 15:59:57 +03:00
parent 6f9cbdfa84
commit 84c732674f
No known key found for this signature in database
GPG key ID: 184A60085ABF17D8
7 changed files with 296 additions and 33 deletions

View file

@ -4,7 +4,7 @@ use openvk\Web\Util\DateTime;
use Nette\Database\Table\ActiveRow;
use openvk\Web\Models\RowModel;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Repositories\{Applications, Comments, Notes, Users, Posts, Photos, Videos, Clubs};
use openvk\Web\Models\Repositories\{Applications, Comments, Notes, Reports, Users, Posts, Photos, Videos, Clubs};
use Chandler\Database\DatabaseConnection as DB;
use Nette\InvalidStateException as ISE;
use Nette\Database\Table\Selection;
@ -81,6 +81,11 @@ class Report extends 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() . "**") : ("Подозрительная активность");
@ -95,6 +100,42 @@ class Report extends RowModel
$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();
}

View file

@ -20,15 +20,22 @@ class Reports
return is_null($ar) ? NULL : new Report($ar);
}
function getReports(int $state = 0, int $page = 1): \Traversable
function getReports(int $state = 0, int $page = 1, ?string $type = NULL, ?bool $pagination = true): \Traversable
{
foreach($this->reports->where(["deleted" => 0])->order("created DESC")->page($page, 15) as $t)
$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]));
return sizeof($this->reports->where(["deleted" => 0, "type" => $state])->group("target_id, type"));
}
function get(int $id): ?Report
@ -46,5 +53,15 @@ class Reports
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;
}

View file

@ -20,15 +20,52 @@ final class ReportPresenter extends OpenVKPresenter
{
$this->assertUserLoggedIn();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
if ($_SERVER["REQUEST_METHOD"] === "POST")
$this->assertNoCSRF();
$this->template->reports = $this->reports->getReports(0, (int)($this->queryParam("p") ?? 1));
$this->template->count = $this->reports->getReportsCount();
$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
@ -52,13 +89,15 @@ final class ReportPresenter extends OpenVKPresenter
exit(json_encode([ "error" => tr("error_segmentation") ]));
if(in_array($this->queryParam("type"), ["post", "photo", "video", "group", "comment", "note", "app", "user"])) {
$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();
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 {

View file

@ -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}
<div class="container_gray">
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{ifset top}
{include top, x => $dat}
{/ifset}
{if sizeof($data) > 0}
<div class="content" n:foreach="$data as $dat">
<table>
<tbody>
<tbody n:attr="id => is_null($table_body_id) ? NULL : $table_body_id">
<tr>
<td valign="top">
<a href="{include link, x => $dat}">

View file

@ -1,8 +1,12 @@
{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}
@ -14,6 +18,12 @@
{/block}
{block top}
{if !is_null($orig)}
<h4>Дубликаты жалобы №{$orig}</h4>
{/if}
{/block}
{* BEGIN ELEMENTS DESCRIPTION *}
{block link|strip|stripHtml}
@ -25,25 +35,26 @@
{/block}
{block name}
{$x->getAuthor()->getCanonicalName()}
Жалоба №{$x->getId()}
{/block}
{block description}
{if $x->getContentType() == "post"}
{_text_of_the_post}: {$x->getContentObject()->getText()}
{elseif $x->getContentType() == "photo"}
{_photo}
{elseif $x->getContentType() == "video"}
{_video}
{elseif $x->getContentType() == "group"}
{_groups}
{elseif $x->getContentType() == "comment"}
{_comment}
{elseif $x->getContentType() == "app"}
{_app}
{elseif $x->getContentType() == "user"}
{_users}
{else}
Unknown
<a href="{$x->getReportAuthor()->getURL()}">
{$x->getReportAuthor()->getCanonicalName()}
</a>
пожаловал{!$x->getReportAuthor()->isFemale() ? 'ся' : 'ась'} на
{if $x->getContentType() === "user"}<a href="{$x->getContentObject()->getURL()}">{/if}
{$x->getContentName()}
{if $x->getContentType() === "user"}</a>{/if}
{if $x->hasDuplicates() && !$orig}
<br />
<b>Другие жалобы на этот контент: <a href="/scumfeed?orig={$x->getId()}">{$x->getDuplicatesCount()} шт.</a></b>
{/if}
{/block}
{block bottom}
<center id="reports-loader" style="display: none; padding: 64px;">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px;">
</center>
{/block}

View file

@ -0,0 +1,145 @@
<style>
.reportsTabs {
display: flex;
flex-wrap: wrap;
justify-content: center;
row-gap: 4px;
gap: 4px;
padding: 8px;
}
.reportsTabs .tab {
display: flex;
flex: 0 0 calc(16.66% - 20px);
justify-content: center;
border-radius: 3px;
padding: 4px;
margin: 0;
cursor: pointer;
}
</style>
<center class="tabs reportsTabs stupid-fix">
<div n:attr="id => ($mode === 'all' ? 'activetabs' : 'ki')" class="tab" mode="all">
<a n:attr="id => ($mode === 'all' ? 'act_tab_a' : 'ki')" mode="all">Все</a>
</div>
<div n:attr="id => ($mode === 'post' ? 'activetabs' : 'ki')" class="tab" mode="post">
<a n:attr="id => ($mode === 'post' ? 'act_tab_a' : 'ki')">Записи</a>
</div>
<div n:attr="id => ($mode === 'photo' ? 'activetabs' : 'ki')" class="tab" mode="photo">
<a n:attr="id => ($mode === 'photo' ? 'act_tab_a' : 'ki')">Фотографии</a>
</div>
<div n:attr="id => ($mode === 'video' ? 'activetabs' : 'ki')" class="tab" mode="video">
<a n:attr="id => ($mode === 'video' ? 'act_tab_a' : 'ki')">Видеозаписи</a>
</div>
<div n:attr="id => ($mode === 'group' ? 'activetabs' : 'ki')" class="tab" mode="group">
<a n:attr="id => ($mode === 'group' ? 'act_tab_a' : 'ki')">Сообщества</a>
</div>
<div n:attr="id => ($mode === 'comment' ? 'activetabs' : 'ki')" class="tab" mode="comment">
<a n:attr="id => ($mode === 'comment' ? 'act_tab_a' : 'ki')">Комментарии</a>
</div>
<div n:attr="id => ($mode === 'note' ? 'activetabs' : 'ki')" class="tab" mode="note">
<a n:attr="id => ($mode === 'note' ? 'act_tab_a' : 'ki')">Заметки</a>
</div>
<div n:attr="id => ($mode === 'app' ? 'activetabs' : 'ki')" class="tab" mode="app">
<a n:attr="id => ($mode === 'app' ? 'act_tab_a' : 'ki')">Приложения</a>
</div>
<div n:attr="id => ($mode === 'user' ? 'activetabs' : 'ki')" class="tab" mode="user">
<a n:attr="id => ($mode === 'user' ? 'act_tab_a' : 'ki')">Пользователи</a>
</div>
</center>
<script>
async function getReports(mode) {
let _content = $(".content").length;
$(".container_gray").empty();
await $.ajax({
type: "POST",
url: `/scumfeed?act=${ mode}`,
data: {
hash: {=$csrfToken}
},
success: (response) => {
if (response?.reports?.length != _content) {
NewNotification("Обратите внимание", "В списке появились новые жалобы. Работа ждёт :)");
}
if (response.reports.length > 0) {
response.reports.forEach((report) => {
$(".container_gray").append(`
<div class="content">
<table>
<tbody>
<tr>
<td valign="top">
<a href="/admin/report${ report.id}">
<center>
<img src="/assets/packages/static/openvk/img/note_icon.png" style="margin-top: 17px;">
</center>
</a>
</td>
<td valign="top" style="width: 100%">
<a href="/admin/report${ report.id}">
<b>
Жалоба №${ report.id}
</b>
</a>
<br>
<a href="${ report.author.url}">
${ report.author.name}
</a>
пожаловал${ report.author.is_female ? "ась" : "ся"} на
${ report.content.type === "user" ? `<a href="${ report.content.url}">` : ''}
${ report.content.name}
${ report.content.type === "user" ? '</a>' : ''}
${ report.duplicates > 0 ? `
<br />
<b>Другие жалобы на этот контент: <a href="/scumfeed?orig=${ report.id}">${ report.duplicates} шт.</a></b>
` : ''}
</td>
<td valign="top" class="action_links" style="width: 150px;">
</td>
</tr>
</tbody>
</table>
</div>
`);
});
} else {
$(".content table").width("100%")
$(".container_gray").html(`
<center style="background: white;border: #DEDEDE solid 1px;">
<span style="color: #707070;margin: 60px 0;display: block;">
{_no_data_description|noescape}
</span>
</center>
`);
}
}
});
}
$(".reportsTabs .tab").on("click", async function () {
let mode = $(this).attr("mode");
$(".reportsTabs #activetabs").attr("id", "ki");
$(".reportsTabs #act_tab_a").attr("id", "ki");
$(`.reportsTabs .tab[mode='${ mode}']`).attr("id", "activetabs");
$(`.reportsTabs .tab[mode='${ mode}'] a`).attr("id", "act_tab_a");
$(".container_gray").hide();
$("#reports-loader").show();
history.pushState(null, null, `/scumfeed?act=${ mode}`);
await getReports(mode);
$(".container_gray").show();
$("#reports-loader").hide();
});
setInterval(async () => {
await getReports($(".reportsTabs #activetabs").attr("mode"));
}, 10000);
</script>

View file

@ -12,6 +12,8 @@
<div class="tabs">{include "../NoSpam/Tabs.xml", mode => "reports"}</div>
<br />
<p>
<b>{$report->getReportAuthor()->getCanonicalName()}</b> пожаловался на <b>{$report->getContentName()}</b>
<br />
<b>{_comment}:</b> {$report->getReason()}
</p>
{include "ViewContent.xml", type => $report->getContentType(), object => $report->getContentObject()}
@ -21,7 +23,7 @@
<form n:if="$report->getContentType() != 'group'" action="/admin/reportAction{$report->getId()}" method="post">
<input type="hidden" name="hash" value="{$csrfToken}"/>
<input type="submit" name="ban" value="{_ban_user_action}" class="button">
<input type="submit" name="delete" value="{_delete}" class="button">
<input n:if="$report->getContentType() !== 'user'" type="submit" name="delete" value="{_delete}" class="button">
<input type="submit" name="ignore" value="{_ignore_report}" class="button">
</form>
<form n:if="$report->getContentType() == 'group'" action="/admin/reportAction{$report->getId()}" method="post">