Prechádzať zdrojové kódy

Add for admins to edit the author and date of post

You can only change it to the user, not to the guest.
The time of a post does not affect its position in the topic (the display order is by id).
The time of the first post affects the creation time of the topic.
Visman 1 rok pred
rodič
commit
537b51d879

+ 9 - 0
app/Controllers/Routing.php

@@ -412,6 +412,15 @@ class Routing
                 'DeletePost'
             );
 
+            if ($user->isAdmin) {
+                $r->add(
+                    $r::DUO,
+                    '/post/{id|i:[1-9]\d*}/change',
+                    'Edit:change',
+                    'ChangeAnD'
+                );
+            }
+
             // сигналы (репорты)
             if (
                 ! $user->isAdmin

+ 17 - 0
app/Core/Func.php

@@ -11,6 +11,8 @@ declare(strict_types=1);
 namespace ForkBB\Core;
 
 use ForkBB\Core\Container;
+use DateTime;
+use DateTimeZone;
 use function \ForkBB\__;
 
 class Func
@@ -254,4 +256,19 @@ class Func
 
         return \array_keys($result);
     }
+
+    /**
+     * Возвращает смещение в секундах для часовой зоны текущего пользователя или 0
+     */
+    public function offset(): int
+    {
+        if (\in_array($this->c->user->timezone, DateTimeZone::listIdentifiers(), true)) {
+            $dateTimeZone = new DateTimeZone($this->c->user->timezone);
+            $dateTime     = new DateTime('now', $dateTimeZone);
+
+            return $dateTime->getOffset();
+        } else {
+            return 0;
+        }
+    }
 }

+ 198 - 0
app/Models/Pages/Edit.php

@@ -17,6 +17,7 @@ use ForkBB\Models\Pages\PostValidatorTrait;
 use ForkBB\Models\Poll\Poll;
 use ForkBB\Models\Post\Post;
 use ForkBB\Models\Topic\Topic;
+use ForkBB\Models\User\User;
 use function \ForkBB\__;
 
 class Edit extends Page
@@ -314,4 +315,201 @@ class Edit extends Page
             $this->c->polls->insert($poll);
         }
     }
+
+    /**
+     * Переводит метку времени в дату/время с учетом часового пояса пользователя
+     */
+    protected function timeToDate(int $timestamp): string
+    {
+        return \gmdate('Y-m-d\TH:i:s', $timestamp + $this->c->Func->offset());
+    }
+
+    /**
+     * Переводит дату/время в метку времени с учетом часового пояса пользователя
+     */
+    protected function dateToTime(string $date): int
+    {
+        return \strtotime("{$date} UTC") - $this->c->Func->offset();
+    }
+
+    /**
+     * Изменение автора и даты
+     */
+    public function change(array $args, string $method): Page
+    {
+        $post = $this->c->posts->load($args['id']);
+
+        if (
+            ! $post instanceof Post
+            || ! $post->canEdit
+        ) {
+            return $this->c->Message->message('Bad request');
+        }
+
+        $topic     = $post->parent;
+        $firstPost = $post->id === $topic->first_post_id;
+        $lastPost  = $post->id === $topic->last_post_id;
+
+        $this->c->Lang->load('post');
+        $this->c->Lang->load('validator');
+
+        if ('POST' === $method) {
+            $v = $this->c->Validator->reset()
+            ->addValidators([
+                'username_check' => [$this, 'vUsernameCheck'],
+            ])->addRules([
+                'token'      => 'token:ChangeAnD',
+                'username'   => 'required|string|username_check',
+                'posted'     => 'required|date',
+                'change_and' => 'required|string',
+            ])->addAliases([
+                'username' => 'Username',
+                'posted'   => 'Posted',
+            ])->addArguments([
+                'token'                   => $args,
+                'username.username_check' => $post->user,
+            ]);
+
+            if ($v->validation($_POST)) {
+                $ids     = [];
+                $upPost  = false;
+
+                // изменить имя автора
+                if (
+                    $this->newUser instanceof User
+                    && $this->newUser->id !== $post->user->id
+                ) {
+                    if (! $post->user->isGuest) {
+                        $ids[] = $post->user->id;
+                    }
+
+                    if (! $this->newUser->isGuest) {
+                        $ids[] = $this->newUser->id;
+                    }
+
+                    $post->poster    = $this->newUser->username;
+                    $post->poster_id = $this->newUser->id;
+                    $upPost          = true;
+                }
+                // изменит время создания
+                if (\abs($post->posted - $this->dateToTime($v->posted)) >= 60) {
+                    $post->posted    = $this->dateToTime($v->posted);
+                    $upPost          = true;
+                }
+
+                if ($upPost) {
+                    $post->edited    = \time();
+                    $post->editor    = $this->user->username;
+                    $post->editor_id = $this->user->id;
+
+                    $this->c->posts->update($post);
+
+                    if (
+                        $firstPost
+                        || $lastPost
+                    ) {
+                        $topic->calcStat();
+                        $this->c->topics->update($topic);
+
+                        if ($lastPost) {
+                            $topic->parent->calcStat();
+                            $this->c->forums->update($topic->parent);
+                        }
+                    }
+                }
+
+                if ($ids) {
+                    $this->c->users->updateCountPosts(...$ids);
+
+                    if ($firstPost) {
+                        $this->c->users->updateCountTopics(...$ids);
+                    }
+                }
+
+                return $this->c->Redirect->url($post->link)->message('Change redirect', FORK_MESS_SUCC);
+            }
+
+            $this->fIswev = $v->getErrors();
+
+            $data = [
+                'username' => $v->username ?: $post->poster,
+                'posted'   => $v->posted ?: $this->timeToDate($post->posted),
+            ];
+        } else {
+            $data = [
+                'username' => $post->poster,
+                'posted'   => $this->timeToDate($post->posted),
+            ];
+        }
+
+        $this->nameTpl   = 'post';
+        $this->onlinePos = 'topic-' . $topic->id;
+        $this->robots    = 'noindex';
+        $this->formTitle = $firstPost ? 'Change AnD topic' : 'Change AnD post';
+        $this->crumbs    = $this->crumbs($this->formTitle, $topic);
+        $this->form      = $this->formAuthorAndDate($data, $args);
+
+        return $this;
+    }
+
+    public function vUsernameCheck(Validator $v, string $username, $attr, User $user): string
+    {
+        if ($username !== $user->username) {
+            $newUser = $this->c->users->loadByName($username, true);
+
+            if ($newUser instanceof User) {
+                $username      = $newUser->username;
+                $this->newUser = $newUser;
+            } else {
+                $v->addError(['User %s does not exist', $username]);
+            }
+        }
+
+        return $username;
+    }
+
+    /**
+     * Возвращает данные для построения формы изменения автора поста и времени создания
+     */
+    protected function formAuthorAndDate(array $data, array $args): ?array
+    {
+        if (! $this->user->isAdmin) {
+            return null;
+        }
+
+        return [
+            'action' => $this->c->Router->link('ChangeAnD', $args),
+            'hidden' => [
+                'token' => $this->c->Csrf->create('ChangeAnD', $args),
+            ],
+            'sets' => [
+                'author-and-date' => [
+                    'fields' => [
+                        'username'=> [
+                            'type'      => 'text',
+                            'minlength' => $this->c->USERNAME['min'],
+                            'maxlength' => $this->c->USERNAME['max'],
+                            'caption'   => 'Username',
+                            'required'  => true,
+                            'pattern'   => $this->c->USERNAME['jsPattern'],
+                            'value'     => $data['username'] ?? null,
+                            'autofocus' => true,
+                        ],
+                        'posted'=> [
+                            'type'      => 'datetime-local',
+                            'caption'   => 'Posted',
+                            'required'  => true,
+                            'value'     => $data['posted'] ?? null,
+                        ],
+                    ],
+                ],
+            ],
+            'btns' => [
+                'change_and' => [
+                    'type'  => 'submit',
+                    'value' => __('Change'),
+                ],
+            ],
+        ];
+    }
 }

+ 13 - 0
app/Models/Post/Post.php

@@ -194,6 +194,19 @@ class Post extends DataModel
         );
     }
 
+    /**
+     * Ссылка на страницу редактирования автора и даты
+     */
+    protected function getlinkAnD(): string
+    {
+        return $this->c->Router->link(
+            'ChangeAnD',
+            [
+                'id' => $this->id,
+            ]
+        );
+    }
+
     /**
      * Статус возможности ответа с цитированием
      */

+ 1 - 9
app/functions.php

@@ -11,8 +11,6 @@ declare(strict_types=1);
 namespace ForkBB;
 
 use ForkBB\Core\Container;
-use DateTime;
-use DateTimeZone;
 use InvalidArgumentException;
 
 /**
@@ -105,13 +103,7 @@ function dt(int $arg, bool $dateOnly = false, string $dateFormat = null, string
     }
 
     if (null === $offset) {
-        if (\in_array($c->user->timezone, DateTimeZone::listIdentifiers(), true)) {
-            $dateTimeZone = new DateTimeZone($c->user->timezone);
-            $dateTime     = new DateTime('now', $dateTimeZone);
-            $offset       = $dateTime->getOffset();
-        } else {
-            $offset      = 0;
-        }
+        $offset = $c->Func->offset();
     }
 
     $arg += $offset;

+ 21 - 0
app/lang/en/post.po

@@ -83,3 +83,24 @@ msgstr "%1$s (%2$s max size)"
 
 msgid "Attachments"
 msgstr "Attachments"
+
+msgid "Change author and date"
+msgstr "Change author and date"
+
+msgid "Change"
+msgstr "Change"
+
+msgid "Posted"
+msgstr "Posted"
+
+msgid "Change AnD topic"
+msgstr "Change author of topic and its date"
+
+msgid "Change AnD post"
+msgstr "Change author of post and its date"
+
+msgid "User %s does not exist"
+msgstr "User '%s' does not exist."
+
+msgid "Change redirect"
+msgstr "Changes done."

+ 3 - 0
app/lang/en/topic.po

@@ -127,3 +127,6 @@ msgstr "%1$s (%2$s max size)"
 
 msgid "Attachments"
 msgstr "Attachments"
+
+msgid "Change author and date"
+msgstr "Change author and date"

+ 21 - 0
app/lang/ru/post.po

@@ -83,3 +83,24 @@ msgstr "%1$s (макс. %2$s)"
 
 msgid "Attachments"
 msgstr "Вложения"
+
+msgid "Change author and date"
+msgstr "Изменить автора и дату"
+
+msgid "Change"
+msgstr "Изменить"
+
+msgid "Posted"
+msgstr "Опубликовано"
+
+msgid "Change AnD topic"
+msgstr "Изменение автора темы и её даты"
+
+msgid "Change AnD post"
+msgstr "Изменение автора сообщения и его даты"
+
+msgid "User %s does not exist"
+msgstr "Нет пользователя с именем '%s'."
+
+msgid "Change redirect"
+msgstr "Изменения выполнены."

+ 3 - 0
app/lang/ru/topic.po

@@ -128,3 +128,6 @@ msgstr "%1$s (макс. %2$s)"
 
 msgid "Attachments"
 msgstr "Вложения"
+
+msgid "Change author and date"
+msgstr "Изменить автора и дату"

+ 1 - 0
app/templates/_default/layouts/form.forkbb.php

@@ -36,6 +36,7 @@
                         @case ('number')
                         @case ('password')
                         @case ('file')
+                        @case ('datetime-local')
                 <input id="id-{{ $key }}" name="{{ $key }}" class="f-ctrl" type="{{ $cur['type'] }}" @foreach ($cur as $k => $v) @if (\in_array($k, ['autofocus', 'disabled', 'multiple', 'readonly', 'required'], true) && ! empty($v)) {!! $k !!} @elseif (\in_array($k, ['accept', 'autocapitalize', 'autocomplete', 'max', 'maxlength', 'min', 'minlength', 'pattern', 'placeholder', 'step', 'title', 'value'], true)) {!! $k !!}="{{ $v }}" @endif @endforeach>
                             @break
                         @case ('textarea')

+ 3 - 0
app/templates/_default/topic.forkbb.php

@@ -62,6 +62,9 @@
         @else
           <span class="f-post-posted"><time datetime="{{ \gmdate('c', $post->posted) }}">{{ dt($post->posted) }}</time></span>
         @endif
+        @if ($post->canEdit && $p->user->isAdmin)
+          <span class="f-post-change" title="{{ __('Change author and date') }}"><a class="f-post-change-a" href="{{ $post->linkAnD }}"><span>&#9881;</span></a></span>
+        @endif
         @if ($post->edited)
           <span class="f-post-edited" title="{{! __(['Last edit', $post->editor, dt($post->edited)]) !}}"><span>{!! __('Edited') !!}</span></span>
         @endif

+ 9 - 2
public/style/ForkBB/style.css

@@ -1534,6 +1534,7 @@ body,
   border-bottom: 0.0625rem solid var(--br-fprimary);
   background-color: var(--bg-post-h);
   font-size: 0.875rem;
+  gap: 0.625rem;
 }
 
 #fork-topic .f-phead-h3,
@@ -1541,17 +1542,23 @@ body,
   display: none;
 }
 
-#fork .f-post-edited > span {
+#fork .f-post-edited > span,
+#fork .f-post-change-a > span {
   display: none;
 }
 
 #fork .f-post-edited::before {
-  padding: 0 0.625rem;
   font-family: "fa";
   font-weight: 400;
   content: "\f044";
 }
 
+#fork .f-post-change-a::before {
+  font-family: "fa";
+  font-weight: 900;
+  content: "\f013";
+}
+
 #fork .f-post-number {
   margin-inline-start: auto;
   word-break: normal;