浏览代码

Add view of uploaded files to admin panel

Visman 2 年之前
父节点
当前提交
8ee1da7acc

+ 1 - 1
app/Controllers/Routing.php

@@ -783,7 +783,7 @@ class Routing
             );
             );
             $r->add(
             $r->add(
                 $r::DUO,
                 $r::DUO,
-                '/admin/uploads',
+                '/admin/uploads[/{page|i:[1-9]\d*}]',
                 'AdminUploads:view',
                 'AdminUploads:view',
                 'AdminUploads'
                 'AdminUploads'
             );
             );

+ 83 - 3
app/Models/Attachment/Attachments.php

@@ -12,18 +12,19 @@ namespace ForkBB\Models\Attachment;
 
 
 use ForkBB\Core\File;
 use ForkBB\Core\File;
 use ForkBB\Core\Image;
 use ForkBB\Core\Image;
-use ForkBB\Models\Manager;
+use ForkBB\Models\Model;
 use ForkBB\Models\Post\Post;
 use ForkBB\Models\Post\Post;
 use ForkBB\Models\PM\PPost;
 use ForkBB\Models\PM\PPost;
 use ForkBB\Models\User\User;
 use ForkBB\Models\User\User;
 use PDO;
 use PDO;
 use RuntimeException;
 use RuntimeException;
 
 
-class Attachments extends Manager
+class Attachments extends Model
 {
 {
     const HTML_CONT = '<!DOCTYPE html><html lang="en"><head><title>.</title></head><body>.</body></html>';
     const HTML_CONT = '<!DOCTYPE html><html lang="en"><head><title>.</title></head><body>.</body></html>';
     const BAD_EXTS  = '%^(?:php.*|phar|[ps]?html?|jsp?|htaccess|htpasswd|f?cgi|svg|)$%i';
     const BAD_EXTS  = '%^(?:php.*|phar|[ps]?html?|jsp?|htaccess|htpasswd|f?cgi|svg|)$%i';
     const FOLDER    = '/upload/';
     const FOLDER    = '/upload/';
+    const PER_PAGE  = 20;
 
 
     /**
     /**
      * Ключ модели для контейнера
      * Ключ модели для контейнера
@@ -230,7 +231,10 @@ class Attachments extends Manager
         return;
         return;
     }
     }
 
 
-    public function recalculate(User $user)
+    /**
+     * Пересчитывает объём файлов пользователя
+     */
+    public function recalculate(User $user): void
     {
     {
         $vars = [
         $vars = [
             ':uid' => $user->id,
             ':uid' => $user->id,
@@ -241,4 +245,80 @@ class Attachments extends Manager
 
 
         $this->c->users->update($user); //???? оптимизировать?
         $this->c->users->update($user); //???? оптимизировать?
     }
     }
+
+    /**
+     * Количество страниц для просмотра файлов
+     */
+    protected function getnumPages(): int
+    {
+        $query = 'SELECT COUNT(id) FROM ::attachments';
+
+        $this->fileCount = (int) $this->c->DB->query($query)->fetchColumn();
+
+        return (int) \ceil(($this->fileCount ?: 1) / self::PER_PAGE);
+    }
+
+    /**
+     * Статус наличия установленной страницы
+     */
+    public function hasPage(): bool
+    {
+        return $this->page > 0 && $this->page <= $this->numPages;
+    }
+
+    /**
+     * Массив страниц
+     */
+    protected function getpagination(): array
+    {
+        return $this->c->Func->paginate(
+            $this->numPages,
+            $this->page,
+            'AdminUploads',
+            ['#' => 'filelist']
+        );
+    }
+
+    /**
+     * Возвращает массив данных с установленной страницы
+     */
+    public function pageData(): array
+    {
+        if (! $this->hasPage()) {
+            throw new InvalidArgumentException('Bad number of displayed page');
+        }
+
+        if (empty($this->fileCount)) {
+            return [];
+        }
+
+        $vars = [
+            ':offset' => ($this->page - 1) * self::PER_PAGE,
+            ':rows'   => self::PER_PAGE,
+        ];
+        $query = "SELECT id
+            FROM ::attachments
+            ORDER BY id DESC
+            LIMIT ?i:rows OFFSET ?i:offset";
+
+        $this->idsList = $this->c->DB->query($query, $vars)->fetchAll(PDO::FETCH_COLUMN);
+
+        if (empty($this->idsList)) {
+            return [];
+        }
+
+        $vars = [
+            ':ids' => $this->idsList,
+        ];
+        $query = 'SELECT * FROM ::attachments WHERE id IN (?ai:ids)';
+
+        $stmt = $this->c->DB->query($query, $vars);
+        $data = [];
+
+        while ($row = $stmt->fetch()) {
+            $data[$row['id']] = $row;
+        }
+
+        return $data;
+    }
 }
 }

+ 117 - 4
app/Models/Pages/Admin/Uploads.php

@@ -12,9 +12,10 @@ namespace ForkBB\Models\Pages\Admin;
 
 
 use ForkBB\Core\Validator;
 use ForkBB\Core\Validator;
 use ForkBB\Models\Page;
 use ForkBB\Models\Page;
+use ForkBB\Models\Attachment\Attachments;
 use ForkBB\Models\Pages\Admin;
 use ForkBB\Models\Pages\Admin;
 use ForkBB\Models\Config\Config;
 use ForkBB\Models\Config\Config;
-use function \ForkBB\__;
+use function \ForkBB\{__, dt, size};
 use RuntimeException;
 use RuntimeException;
 
 
 class Uploads extends Admin
 class Uploads extends Admin
@@ -57,9 +58,19 @@ class Uploads extends Admin
             $this->fIswev = $v->getErrors();
             $this->fIswev = $v->getErrors();
         }
         }
 
 
-        $this->nameTpl         = 'admin/uploads';
-        $this->aIndex          = 'uploads';
-        $this->formUploads     = $this->formUploads($config);
+        $this->nameTpl     = 'admin/uploads';
+        $this->aIndex      = 'uploads';
+        $this->formUploads = $this->formUploads($config);
+
+        $attachments       = $this->c->attachments;
+        $attachments->page = $args['page'] ?: 1;
+        $this->pagination  = $attachments->pagination;
+
+        if ($attachments->hasPage()) {
+            $this->formFileList = $this->formFileList($attachments, $args);
+        } else {
+            $this->badPage = $attachments->page;
+        }
 
 
         return $this;
         return $this;
     }
     }
@@ -156,4 +167,106 @@ class Uploads extends Admin
             return \implode(',', $result);
             return \implode(',', $result);
         }
         }
     }
     }
+
+    /**
+     * Подготавливает массив данных для формы
+     */
+    protected function formFileList(Attachments $attachments, array $args): array
+    {
+        $data = $attachments->pageData();
+        $uIds = [];
+
+        foreach ($attachments->idsList as $id) {
+            if (isset($data[$id])) {
+                $uid        = $data[$id]['uid'];
+                $uIds[$uid] = $uid;
+            }
+        }
+
+        $users = $this->c->users->loadByIds($uIds);
+
+        $form = [/*
+            'action' => $this->c->Router->link('AdminUploads', $args),
+            'hidden' => [
+                'token' => $this->c->Csrf->create('AdminUploads', $args),
+            ],*/
+            'sets'   => [],
+            /*'btns'   => [],*/
+        ];
+
+        $ids = $attachments->idsList;
+
+        \array_unshift($ids, 0);
+
+        foreach ($ids as $id) {
+            $att    = $data[$id] ?? null;
+            $user   = isset($att['uid'], $users[$att['uid']]) ? $users[$att['uid']] : null;
+            $fields = [];
+
+            $fields["f{$id}-wrap"] = [
+                'class' => ['main-wrap'],
+                'type'  => 'wrap',
+            ];
+            $y = isset($att['path']);
+            $fields["f{$id}-file"] = [
+                'class'   => ['filelist', 'file'],
+                'type'    => $y ? 'include' : 'str',
+                'caption' => 'File head',
+                'value'   => $y ? \basename($att['path']) : '',
+                'title'   => $y ? $att['path'] : '',
+                'href'    => $y ? $this->c->PUBLIC_URL . $attachments::FOLDER . $att['path'] : '',
+                'include' => 'admin/uploads_file',
+            ];
+            $fields["f{$id}-size"] = [
+                'class'   => ['filelist', 'size'],
+                'type'    => 'str',
+                'caption' => 'Size head',
+                'value'   => isset($att['size_kb']) ? size(1024 * ($att['size_kb'] ?: 1)) : '',
+            ];
+            $y = isset($att['created']);
+            $fields["f{$id}-created"] = [
+                'class'   => ['filelist', 'created'],
+                'type'    => $y ? 'link' : 'str',
+                'caption' => 'Created head',
+                'value'   => $y ? dt($att['created']) : '',
+                'title'   => $y ? $att['uip'] : '',
+                'href'    => $y ? $this->c->Router->link('AdminHost', ['ip' => $att['uip']]) : '',
+            ];
+
+            if ($user) {
+                $fields["f{$id}-user"] = [
+                    'class'   => ['filelist', 'user'],
+                    'type'    => 'link',
+                    'caption' => 'User head',
+                    'value'   => $user->username,
+                    'href'    => $this->c->Router->link('User', ['id' => $user->id, 'name' => $user->username]),
+                ];
+            } else {
+                $fields["f{$id}-user"] = [
+                    'class'   => ['filelist', 'user'],
+                    'type'    => 'str',
+                    'caption' => 'User head',
+                    'value'   => $id ? 'User #' . ($att['uid'] ?: '??') : '',
+                ];
+            }
+            $fields[] = [
+                'type' => 'endwrap',
+            ];
+            $fields["f{$id}-action"] = [
+                'class'   => ['action'],
+                'caption' => 'Action',
+                'type'    => 'str',
+                'value'   => $id ? 'X' : '',
+            ];
+
+
+            $form['sets']["f{$id}"] = [
+                'class'  => ['filelist'],
+                'legend' => (string) $id,
+                'fields' => $fields,
+            ];
+        }
+
+        return $form;
+    }
 }
 }

+ 18 - 0
app/lang/en/admin_uploads.po

@@ -38,3 +38,21 @@ msgstr "Output image types"
 
 
 msgid "Output image types help"
 msgid "Output image types help"
 msgstr "Types of pictures to save to this site, separated by commas. If a picture of another type is loaded, it will be converted to the first type from this list."
 msgstr "Types of pictures to save to this site, separated by commas. If a picture of another type is loaded, it will be converted to the first type from this list."
+
+msgid "Page %s missing"
+msgstr "Page %s missing."
+
+msgid "File list head"
+msgstr "File list"
+
+msgid "File head"
+msgstr "File"
+
+msgid "Size head"
+msgstr "Size"
+
+msgid "Created head"
+msgstr "Created"
+
+msgid "User head"
+msgstr "Author"

+ 18 - 0
app/lang/ru/admin_uploads.po

@@ -38,3 +38,21 @@ msgstr "Вых. типы картинок"
 
 
 msgid "Output image types help"
 msgid "Output image types help"
 msgstr "Типы картинок для сохранения на этот сайт, через запятую. Если загружена картинка другого типа, то она будет конвертирована в первый тип из этого списка."
 msgstr "Типы картинок для сохранения на этот сайт, через запятую. Если загружена картинка другого типа, то она будет конвертирована в первый тип из этого списка."
+
+msgid "Page %s missing"
+msgstr "Страница %s отсутствует."
+
+msgid "File list head"
+msgstr "Список файлов"
+
+msgid "File head"
+msgstr "Файл"
+
+msgid "Size head"
+msgstr "Размер"
+
+msgid "Created head"
+msgstr "Создан"
+
+msgid "User head"
+msgstr "Автор"

+ 45 - 0
app/templates/admin/uploads.forkbb.php

@@ -1,3 +1,24 @@
+@section ('pagination')
+    @if ($p->pagination)
+        <nav class="f-pages">
+        @foreach ($p->pagination as $cur)
+            @if ($cur[2])
+          <a class="f-page active" href="{{ $cur[0] }}">{{ $cur[1] }}</a>
+            @elseif ('info' === $cur[1])
+          <span class="f-pinfo">{!! __($cur[0]) !!}</span>
+            @elseif ('space' === $cur[1])
+          <span class="f-page f-pspacer">{!! __('Spacer') !!}</span>
+            @elseif ('prev' === $cur[1])
+          <a rel="prev" class="f-page f-pprev" href="{{ $cur[0] }}" title="{{ __('Previous') }}"><span>{!! __('Previous') !!}</span></a>
+            @elseif ('next' === $cur[1])
+          <a rel="next" class="f-page f-pnext" href="{{ $cur[0] }}" title="{{ __('Next') }}"><span>{!! __('Next') !!}</span></a>
+            @else
+          <a class="f-page" href="{{ $cur[0] }}">{{ $cur[1] }}</a>
+            @endif
+        @endforeach
+        </nav>
+    @endif
+@endsection
 @extends ('layouts/admin')
 @extends ('layouts/admin')
       <section id="fork-uploads" class="f-admin">
       <section id="fork-uploads" class="f-admin">
         <h2>{!! __('Uploads head') !!}</h2>
         <h2>{!! __('Uploads head') !!}</h2>
@@ -7,3 +28,27 @@
 @endif
 @endif
         </div>
         </div>
       </section>
       </section>
+@if ($p->pagination)
+      <div id="filelist" class="f-nav-links">
+        <div class="f-nlinks">
+    @yield ('pagination')
+        </div>
+      </div>
+@endif
+      <section id="fork-uploads-files" class="f-admin">
+        <h2>{!! __('File list head') !!}</h2>
+        <div class="f-fdiv">
+@if (null !== $p->badPage && $iswev = [FORK_MESS_ERR => [['Page %s missing', $p->badPage]]])
+    @include ('layouts/iswev')
+@elseif ($form = $p->formFileList)
+    @include ('layouts/form')
+@endif
+        </div>
+      </section>
+@if ($p->pagination)
+      <div class="f-nav-links">
+        <div class="f-nlinks">
+    @yield ('pagination')
+        </div>
+      </div>
+@endif

+ 4 - 0
app/templates/admin/uploads_file.forkbb.php

@@ -0,0 +1,4 @@
+<a id="id-{{ $key }}" class="f-link" href="{{ $cur['href'] }}" @if ($cur['rel']) rel="{{ $cur['rel'] }}" @endif title="{{ $cur['title'] or $cur['value'] }}">{{ $cur['value'] }}</a>
+@if (\preg_match('%\.(webp|avif|jpe?g|gif|png|bmp)$%i', $cur['href']))
+<span class="f-filelist-image-span"><a href="{{ $cur['href'] }}"><img class="f-filelist-image" src="{{ $cur['href'] }}" alt="{{ $cur['value'] }}"></a></span>
+@endif

+ 153 - 0
public/style/ForkBB/admin.css

@@ -1068,3 +1068,156 @@
 #forka .f-field-provider.f-field-allow .f-flblch {
 #forka .f-field-provider.f-field-allow .f-flblch {
   height: 2rem;
   height: 2rem;
 }
 }
+
+/****************************************/
+/* Админка/Загрузки                     */
+/****************************************/
+#fork-uploads + #filelist {
+  margin-bottom: 1rem;
+}
+
+#fork-uploads-files .f-fleg {
+  display: none;
+}
+
+#fork-uploads-files .f-fs-filelist {
+  display: flex;
+}
+
+#fork-uploads-files .f-wrap-main-wrap {
+  width: calc(100% - 3rem);
+}
+
+#fork-uploads-files .f-field-action {
+  width: 3rem;
+}
+
+#fork-uploads-files .f-field-action > dd {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  width: 100%;
+}
+
+#fork-uploads-files .f-filelist-image {
+  max-width: 10rem;
+  max-height: 10rem;
+  display: block;
+}
+
+#fork-uploads-files .f-field-action {
+  border-inline-start: 0.0625rem dotted #AA7939;
+}
+
+@media screen and (max-width: 85.9999rem) {
+  #fork-uploads-files .f-field-action > dt {
+    display: none;
+  }
+
+  #fork-uploads-files .f-field-file,
+  #fork-uploads-files .f-field-size,
+  #fork-uploads-files .f-field-created {
+    border-bottom: 0;
+  }
+
+  #fork-uploads-files .f-wrap-main-wrap .f-ycaption::after {
+    content: ": ";
+  }
+
+  #fork-uploads-files .f-fs-filelist:first-of-type {
+    display: none;
+  }
+}
+
+@media screen and (max-width: 35.9999rem) {
+  #fork-uploads-files .f-field-filelist > dt,
+  #fork-uploads-files .f-field-filelist > dd,
+  #fork-uploads-files .f-wrap-main-wrap .f-ycaption,
+  #fork-uploads-files .f-wrap-main-wrap .f-str {
+    display: inline;
+  }
+}
+
+@media screen and (min-width: 36rem) and (max-width: 85.9999rem) {
+  #fork-uploads-files .f-field-filelist > dt {
+    width: 8rem;
+  }
+
+  #fork-uploads-files .f-field-filelist > dd {
+    width: calc(100% - 8rem);
+  }
+}
+
+@media screen and (min-width: 86rem)  {
+  #fork-uploads-files .f-wrap-main-wrap {
+    display: flex;
+  }
+
+  #fork-uploads-files .f-field-filelist {
+    flex-direction: column;
+    justify-content: space-around;
+  }
+
+  #fork-uploads-files .f-field-filelist > dt{
+    width: 100%;
+    text-align: center;
+  }
+
+  #fork-uploads-files .f-field-filelist > dd {
+    display: flex;
+    gap: 0.3125rem;
+    align-items: center;
+    height: 100%;
+    width: 100%;
+  }
+
+  #fork-uploads-files .f-ycaption {
+    font-weight: bold;
+  }
+
+  #fork-uploads-files .f-field-file {
+    width: 45%;
+  }
+
+  #fork-uploads-files .f-field-file > dd {
+    justify-content: space-between;
+  }
+
+  #fork-uploads-files .f-field-size {
+    width: 10%;
+    border-inline-start: 0.0625rem dotted #AA7939;
+  }
+
+  #fork-uploads-files .f-field-size > dd {
+    justify-content: flex-end;
+  }
+
+  #fork-uploads-files .f-field-created {
+    width: 21%;
+    border-inline-start: 0.0625rem dotted #AA7939;
+  }
+
+  #fork-uploads-files .f-field-created > dd {
+    justify-content: center;
+  }
+
+  #fork-uploads-files .f-field-user {
+    width: 24%;
+    border-inline-start: 0.0625rem dotted #AA7939;
+  }
+
+  #fork-uploads-files .f-fs-filelist:not(:first-of-type) dt {
+    display: none;
+  }
+
+  #fork-uploads-files .f-fs-filelist:first-of-type {
+    overflow: hidden;
+    white-space: nowrap;
+  }
+
+  #fork-uploads-files .f-fs-filelist:first-of-type dt {
+    display: block;
+    width: 100%;
+  }
+}