mirror of
https://github.com/openvk/openvk
synced 2025-07-02 22:09:53 +03:00
Log posts changes
This commit is contained in:
parent
504cedfb1f
commit
016e9da182
12 changed files with 314 additions and 3 deletions
|
@ -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;
|
||||
}
|
||||
|
|
71
Web/Models/Entities/PostChangeRecord.php
Normal file
71
Web/Models/Entities/PostChangeRecord.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\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;
|
||||
}
|
||||
}
|
45
Web/Models/Repositories/PostsChanges.php
Normal file
45
Web/Models/Repositories/PostsChanges.php
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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 применена.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
35
Web/Presenters/templates/Wall/PostHistory.xml
Normal file
35
Web/Presenters/templates/Wall/PostHistory.xml
Normal 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}
|
41
Web/Presenters/templates/components/postChange.xml
Normal file
41
Web/Presenters/templates/components/postChange.xml
Normal 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">
|
||||
| Применить
|
||||
</a>
|
||||
<span class="nobold" n:if="$post->getChangeId() == $change->getId() AND $post->getChangeType() == 'old'">
|
||||
| Текущая версия
|
||||
</span>
|
||||
<hr size="1" color="#ddd">
|
||||
{$change->getNewContent()|noescape}
|
||||
<a n:if="$change->canBeApplied('new')" href="/wall{$post->getPrettyId()}/history/restore{$change->getId()}/new">
|
||||
| Применить
|
||||
</a>
|
||||
<span class="nobold" n:if="$post->getChangeId() == $change->getId() AND $post->getChangeType() == 'new'">
|
||||
| Текущая версия
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
12
Web/Presenters/templates/components/postChanges.xml
Normal file
12
Web/Presenters/templates/components/postChanges.xml
Normal 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}
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
15
install/sqls/00031-posts-edit.sql
Normal file
15
install/sqls/00031-posts-edit.sql
Normal 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;
|
|
@ -54,6 +54,7 @@ openvk:
|
|||
maxSize: 60000
|
||||
processingLimit: 3000
|
||||
emojiProcessingLimit: 1000
|
||||
logChanges: true
|
||||
commerce: false
|
||||
ton:
|
||||
enabled: false
|
||||
|
|
Loading…
Reference in a new issue