Log posts changes

This commit is contained in:
n1rwana 2022-08-28 21:22:11 +03:00
parent 504cedfb1f
commit 016e9da182
12 changed files with 314 additions and 3 deletions

View file

@ -174,6 +174,16 @@ class Post extends Postable
$this->unwire();
$this->save();
}
function getChangeId(): int
{
return $this->getRecord()->change_id;
}
function getChangeType(): string
{
return $this->getRecord()->change_type == 2 ? "new" : "old";
}
use Traits\TRichText;
}

View 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\Entities\{User, Club, Post};
use openvk\Web\Models\Repositories\{Users, Clubs, Posts};
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
class PostChangeRecord extends RowModel
{
protected $tableName = "posts_changes";
function getId(): int
{
return $this->getRecord()->id;
}
function getWid(): int
{
return $this->getRecord()->wall_id;
}
function getAuthorType(): string
{
return $this->getWid() < 0 ? "club" : "user";
}
function getVid(): int
{
return $this->getRecord()->virtual_id;
}
function getPost(): ?Post
{
return (new Posts)->getPostById($this->getWid(), $this->getVid());
}
function getAuthor()
{
if ($this->getAuthorType() === "club")
return (new Clubs)->get($this->getWid());
return (new Users)->get($this->getWid());
}
function getOldContent(): ?string
{
return $this->getRecord()->oldContent;
}
function getNewContent(): ?string
{
return $this->getRecord()->newContent;
}
function getCreationDate(): DateTime
{
return new DateTime($this->getRecord()->created);
}
function canBeApplied(string $type): bool
{
$post = $this->getPost();
if ($post->getChangeId() == $this->getId())
if ($post->getChangeType() == $type) return false;
return true;
}
}

View file

@ -0,0 +1,45 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\{Post, User, PostChangeRecord};
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
class PostsChanges
{
private $context;
private $changes;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->changes = $this->context->table("posts_changes");
}
function toChangeRecord(?ActiveRow $ar): ?PostChangeRecord
{
return is_null($ar) ? NULL : new PostChangeRecord($ar);
}
function get(int $id): ?PostChangeRecord
{
return $this->toChangeRecord($this->changes->get($id));
}
function getListByWid(int $wid): \Traversable
{
foreach ($this->changes->where("wall_id", $wid)->fetch() as $record)
yield new PostChangeRecord($record);
}
function getAllHistoryById(int $wid, int $vid): \Traversable
{
foreach($this->changes->where(["wall_id" => $wid, "virtual_id" => $vid]) as $record)
yield new PostChangeRecord($record);
}
function getHistoryById(int $wid, int $vid, int $page = 1): \Traversable
{
foreach($this->changes->where(["wall_id" => $wid, "virtual_id" => $vid])->page($page, 5) as $record)
yield new PostChangeRecord($record);
}
}

View file

@ -1,8 +1,8 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Post, Photo, Video, Club, User};
use openvk\Web\Models\Entities\{Post, Photo, Video, Club, User, PostChangeRecord};
use openvk\Web\Models\Entities\Notifications\{RepostNotification, WallPostNotification};
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums};
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, PostsChanges};
use Chandler\Database\DatabaseConnection;
use Nette\InvalidStateException as ISE;
use Bhaktaraz\RSSGenerator\Item;
@ -12,10 +12,12 @@ use Bhaktaraz\RSSGenerator\Channel;
final class WallPresenter extends OpenVKPresenter
{
private $posts;
private $changes;
function __construct(Posts $posts)
function __construct(Posts $posts, PostsChanges $changes)
{
$this->posts = $posts;
$this->changes = $changes;
parent::__construct();
}
@ -274,10 +276,24 @@ final class WallPresenter extends OpenVKPresenter
try {
if ($editTarget) {
$post = $this->posts->getPostById($this->user->id, $editTarget);
$changes_record = new PostChangeRecord;
if (OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["logChanges"]) {
$changes_record->setWall_id($wall);
$changes_record->setVirtual_id($editTarget);
$changes_record->setOldContent($post->getText());
$changes_record->setNewContent($this->postParam("text"));
$changes_record->setCreated(time());
$changes_record->save();
}
$post->setEdited(time());
$post->setContent($this->postParam("text"));
$post->setFlags($flags);
$post->setNsfw($this->postParam("nsfw") === "on");
$post->setChange_id($changes_record->getId());
$post->setChange_type(2);
$post->save();
} else {
$post = new Post;
@ -419,4 +435,56 @@ final class WallPresenter extends OpenVKPresenter
# TODO localize message based on language and ?act=(un)pin
$this->flashFail("succ", tr("information_-1"), tr("changes_saved_comment"));
}
function renderPostHistory(int $wall, int $post_id): void
{
$this->assertUserLoggedIn();
if(!$this->user->identity->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL))
$this->flashFail("err", tr("error"), tr("forbidden"));
$post = $this->posts->getPostById($wall, $post_id);
if (!$post) $this->notFound();
$this->template->post = $post;
if ($post->getTargetWall() > 0) {
$this->template->wallOwner = (new Users)->get($post->getTargetWall());
$this->template->isWallOfGroup = false;
if($this->template->wallOwner->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
} else {
$this->template->wallOwner = (new Clubs)->get(abs($post->getTargetWall()));
$this->template->isWallOfGroup = true;
}
$this->template->cPage = (int) ($_GET["p"] ?? 1);
$this->template->cCount = sizeof(iterator_to_array($this->changes->getAllHistoryById($wall, $post_id)));
$this->template->changes = iterator_to_array($this->changes->getHistoryById($wall, $post_id, $this->template->cPage));
$this->template->cAmount = sizeof($this->template->changes);
}
function renderPostHistoryRestore(int $wall, int $post_id, int $change_id, string $type): void
{
$this->assertUserLoggedIn();
if(!$this->user->identity->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL))
$this->flashFail("err", tr("error"), tr("forbidden"));
$post = $this->posts->getPostById($wall, $post_id);
$change = $this->changes->get($change_id);
if ($type == "old")
$post->setContent($change->getOldContent());
else
$post->setContent($change->getNewContent());
$post->setChange_Id($change->getId());
$post->setChange_Type($type == "new" ? 2 : 1);
$post->save();
$this->flashFail("succ", "Успех", "Версия #$change_id применена.");
}
}

View file

@ -31,5 +31,13 @@
{/if}
<a n:if="$canDelete ?? false" class="profile_link" style="display:block;width:96%;" href="/wall{$post->getPrettyId()}/delete">{_delete}</a>
<a
n:if="$thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL) AND $post->getEditTime() AND OPENVK_ROOT_CONF['openvk']['preferences']['wall']['logChanges']"
style="display:block;width:96%;"
class="profile_link"
href="/wall{$post->getPrettyId()}/history"
>
История изменений
</a>
</div>
{/block}

View file

@ -0,0 +1,35 @@
{extends "../@layout.xml"}
{block title}{_post}{/block}
{block header}
<a href="{$wallOwner->getURL()}">
{$wallOwner->getCanonicalName()}
</a>
»
<a href="/wall{$wallOwner->getId() * ($isWallOfGroup ? -1 : 1)}">
{_wall}
</a>
»
{_post}
{/block}
{block content}
{include "../components/post.xml", post => $post, forceNoCommentsLink => TRUE, forceNoDeleteLink => TRUE}
<hr/>
<div style="float: left; min-height: 100px; width: 68%;">
{include "../components/postChanges.xml",
changes => $changes,
count => $cCount,
page => $cPage,
model => "posts",
parent => $post }
</div>
<div style="float: left; min-height: 100px; width: 32%;">
<h4>{_actions}</h4>
{if isset($thisUser)}
{var $canDelete = $post->canBeDeletedBy($thisUser)}
{/if}
<a n:if="$canDelete ?? false" class="profile_link" style="display:block;width:96%;" href="/wall{$post->getPrettyId()}/delete">{_delete}</a>
</div>
{/block}

View file

@ -0,0 +1,41 @@
{var $author = $change->getAuthor()}
<table border="0" style="font-size: 11px;">
<tbody>
<tr>
<td width="54" valign="top">
<a href="{$author->getURL()}">
<img src="{$author->getAvatarURL('miniscule')}" width="50" />
<span n:if="!$post->isPostedOnBehalfOfGroup() && !($compact ?? false) && $author->isOnline()" class="post-online">{_online}</span>
</a>
</td>
<td width="100%" valign="top">
<div class="post-author">
<a href="{$author->getURL()}"><b>
{$author->getCanonicalName()}
</b></a>
<img n:if="$author->isVerified()" class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png"><br/>
</div>
<div class="post-content" id="{$change->getId()}">
<div class="text" id="text{$change->getId()}">
{$change->getOldContent()|noescape}
<a n:if="$change->canBeApplied('old')" href="/wall{$post->getPrettyId()}/history/restore{$change->getId()}/old">
&nbsp;|&nbsp; Применить
</a>
<span class="nobold" n:if="$post->getChangeId() == $change->getId() AND $post->getChangeType() == 'old'">
&nbsp;|&nbsp; Текущая версия
</span>
<hr size="1" color="#ddd">
{$change->getNewContent()|noescape}
<a n:if="$change->canBeApplied('new')" href="/wall{$post->getPrettyId()}/history/restore{$change->getId()}/new">
&nbsp;|&nbsp; Применить
</a>
<span class="nobold" n:if="$post->getChangeId() == $change->getId() AND $post->getChangeType() == 'new'">
&nbsp;|&nbsp; Текущая версия
</span>
</div>
</div>
</td>
</tr>
</tbody>
</table>

View file

@ -0,0 +1,12 @@
<h4 n:if="$showTitle ?? true">История ({$cAmount})</h4>
{if $cCount > 0}
{foreach $changes as $change}
{include "postChange.xml", change => $change, post => $post}
{/foreach}
<div style="margin-top: 11px;">
{include "paginator.xml", conf => (object) ["page" => $cPage, "count" => $cCount, "amount" => $cAmount, "perPage" => 5]}
</div>
{else}
Ничего не найдено.
{/if}

View file

@ -42,3 +42,4 @@ services:
- openvk\Web\Models\Repositories\Topics
- openvk\Web\Models\Repositories\Applications
- openvk\Web\Models\Repositories\ContentSearchRepository
- openvk\Web\Models\Repositories\PostsChanges

View file

@ -131,6 +131,10 @@ routes:
handler: "Wall->delete"
- url: "/wall{num}_{num}/pin"
handler: "Wall->pin"
- url: "/wall{num}_{num}/history"
handler: "Wall->postHistory"
- url: "/wall{num}_{num}/history/restore{num}/{text}"
handler: "Wall->postHistoryRestore"
- url: "/blob_{text}/{?path}.{text}"
handler: "Blob->file"
placeholders:

View file

@ -0,0 +1,15 @@
CREATE TABLE `posts_changes` (
`id` bigint NOT NULL,
`wall_id` bigint NOT NULL,
`virtual_id` bigint NOT NULL,
`oldContent` longtext NOT NULL,
`newContent` longtext NOT NULL,
`created` bigint NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
ALTER TABLE `posts_changes`
ADD PRIMARY KEY (`id`);
ALTER TABLE `posts_changes`
MODIFY `id` bigint NOT NULL AUTO_INCREMENT;
COMMIT;

View file

@ -54,6 +54,7 @@ openvk:
maxSize: 60000
processingLimit: 3000
emojiProcessingLimit: 1000
logChanges: true
commerce: false
ton:
enabled: false