Pārlūkot izejas kodu

Upload improvements

Sergio Brighenti 5 gadi atpakaļ
vecāks
revīzija
ac4429fe0c

+ 1 - 0
app/Controllers/Controller.php

@@ -71,6 +71,7 @@ abstract class Controller
         if ($this->getSetting('quota_enabled', 'off') === 'on') {
         if ($this->getSetting('quota_enabled', 'off') === 'on') {
             if ($max < 0) {
             if ($max < 0) {
                 $this->session->set('max_disk_quota', '∞');
                 $this->session->set('max_disk_quota', '∞');
+                $this->session->set('percent_disk_quota', null);
             } else {
             } else {
                 $this->session->set('max_disk_quota', humanFileSize($max));
                 $this->session->set('max_disk_quota', humanFileSize($max));
                 $this->session->set('percent_disk_quota', round(($current * 100) / $max));
                 $this->session->set('percent_disk_quota', round(($current * 100) / $max));

+ 153 - 70
app/Controllers/UploadController.php

@@ -2,138 +2,221 @@
 
 
 namespace App\Controllers;
 namespace App\Controllers;
 
 
+use App\Exceptions\ValidationException;
 use Exception;
 use Exception;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
 use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\UploadedFileInterface;
 
 
 class UploadController extends Controller
 class UploadController extends Controller
 {
 {
+    private $json = [
+        'message' => null,
+        'version' => PLATFORM_VERSION,
+    ];
+
     /**
     /**
-     * @param  Request  $request
      * @param  Response  $response
      * @param  Response  $response
      *
      *
      * @return Response
      * @return Response
+     * @throws \Twig\Error\LoaderError
      * @throws \Twig\Error\RuntimeError
      * @throws \Twig\Error\RuntimeError
      * @throws \Twig\Error\SyntaxError
      * @throws \Twig\Error\SyntaxError
-     *
-     * @throws \Twig\Error\LoaderError
      */
      */
-    public function webUpload(Request $request, Response $response): Response
+    public function uploadWebPage(Response $response): Response
     {
     {
-        $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $this->session->get('user_id'))->fetch();
-
-        if ($user->token === null || $user->token === '') {
-            $this->session->alert(lang('no_upload_token'), 'danger');
-
-            return redirect($response, $request->getHeaderLine('Referer'));
-        }
-
-        return view()->render($response, 'upload/web.twig', [
-            'user' => $user,
-        ]);
+        return view()->render($response, 'upload/web.twig');
     }
     }
 
 
     /**
     /**
      * @param  Request  $request
      * @param  Request  $request
      * @param  Response  $response
      * @param  Response  $response
-     *
      * @return Response
      * @return Response
      * @throws \Slim\Exception\HttpNotFoundException
      * @throws \Slim\Exception\HttpNotFoundException
      * @throws \Slim\Exception\HttpUnauthorizedException
      * @throws \Slim\Exception\HttpUnauthorizedException
+     * @throws Exception
      */
      */
-    public function upload(Request $request, Response $response): Response
+    public function uploadWeb(Request $request, Response $response): Response
     {
     {
-        $json = [
-            'message' => null,
-            'version' => PLATFORM_VERSION,
-        ];
-
         if ($this->config['maintenance']) {
         if ($this->config['maintenance']) {
-            $json['message'] = 'Endpoint under maintenance.';
+            $this->json['message'] = 'Endpoint under maintenance.';
 
 
-            return json($response, $json, 503);
+            return json($response, $this->json, 503);
         }
         }
 
 
-        if ($request->getServerParams()['CONTENT_LENGTH'] > stringToBytes(ini_get('post_max_size'))) {
-            $json['message'] = 'File too large (post_max_size too low?).';
+        try {
+            $file = $this->validateFile($request, $response);
+
+            $user = $this->getUser($request, $this->session->get('user_id'));
 
 
-            return json($response, $json, 400);
+            $this->validateUser($request, $response, $file, $user);
+        } catch (ValidationException $e) {
+            return $e->response();
         }
         }
 
 
-        $file = array_values($request->getUploadedFiles());
-        /** @var \Psr\Http\Message\UploadedFileInterface|null $file */
-        $file = $file[0] ?? null;
+        if (!$this->updateUserQuota($request, $user->id, $file->getSize())) {
+            $this->json['message'] = 'User disk quota exceeded.';
 
 
-        if ($file === null) {
-            $json['message'] = 'Request without file attached.';
+            return json($response, $this->json, 507);
+        }
 
 
-            return json($response, $json, 400);
+        try {
+            $response = $this->saveMedia($response, $file, $user);
+            $this->setSessionQuotaInfo($user->current_disk_quota + $file->getSize(), $user->max_disk_quota);
+        } catch (Exception $e) {
+            $this->updateUserQuota($request, $user->id, $file->getSize(), true);
+            throw $e;
         }
         }
 
 
-        if ($file->getError() === UPLOAD_ERR_INI_SIZE) {
-            $json['message'] = 'File too large (upload_max_filesize too low?).';
+        return $response;
+    }
 
 
-            return json($response, $json, 400);
+    /**
+     * @param  Request  $request
+     * @param  Response  $response
+     *
+     * @return Response
+     * @throws \Slim\Exception\HttpNotFoundException
+     * @throws \Slim\Exception\HttpUnauthorizedException
+     * @throws Exception
+     */
+    public function uploadEndpoint(Request $request, Response $response): Response
+    {
+        if ($this->config['maintenance']) {
+            $this->json['message'] = 'Endpoint under maintenance.';
+
+            return json($response, $this->json, 503);
+        }
+
+        try {
+            $file = $this->validateFile($request, $response);
+        } catch (ValidationException $e) {
+            return $e->response();
         }
         }
 
 
         if (param($request, 'token') === null) {
         if (param($request, 'token') === null) {
-            $json['message'] = 'Token not specified.';
+            $this->json['message'] = 'Token not specified.';
 
 
-            return json($response, $json, 400);
+            return json($response, $this->json, 400);
         }
         }
 
 
         $user = $this->database->query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', param($request, 'token'))->fetch();
         $user = $this->database->query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', param($request, 'token'))->fetch();
 
 
         if (!$user) {
         if (!$user) {
-            $json['message'] = 'Token specified not found.';
+            $this->json['message'] = 'Token specified not found.';
 
 
-            return json($response, $json, 404);
+            return json($response, $this->json, 404);
         }
         }
 
 
-        if (!$user->active) {
-            $json['message'] = 'Account disabled.';
-
-            return json($response, $json, 401);
+        try {
+            $this->validateUser($request, $response, $file, $user);
+        } catch (ValidationException $e) {
+            return $e->response();
         }
         }
 
 
         if (!$this->updateUserQuota($request, $user->id, $file->getSize())) {
         if (!$this->updateUserQuota($request, $user->id, $file->getSize())) {
-            $json['message'] = 'User disk quota exceeded.';
+            $this->json['message'] = 'User disk quota exceeded.';
 
 
-            return json($response, $json, 507);
+            return json($response, $this->json, 507);
         }
         }
 
 
         try {
         try {
-            do {
-                $code = humanRandomString();
-            } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
+            $response = $this->saveMedia($response, $file, $user);
+        } catch (Exception $e) {
+            $this->updateUserQuota($request, $user->id, $file->getSize(), true);
+            throw $e;
+        }
+        return $response;
+    }
 
 
-            $published = 1;
-            if ($this->getSetting('hide_by_default') === 'on') {
-                $published = 0;
-            }
+    /**
+     * @param  Request  $request
+     * @param  Response  $response
+     * @return UploadedFileInterface
+     * @throws ValidationException
+     */
+    protected function validateFile(Request $request, Response $response)
+    {
+        if ($request->getServerParams()['CONTENT_LENGTH'] > stringToBytes(ini_get('post_max_size'))) {
+            $this->json['message'] = 'File too large (post_max_size too low?).';
 
 
-            $fileInfo = pathinfo($file->getClientFilename());
-            $storagePath = "$user->user_code/$code.$fileInfo[extension]";
+            throw new ValidationException(json($response, $this->json, 400));
+        }
 
 
-            $this->storage->writeStream($storagePath, $file->getStream()->detach());
+        $file = array_values($request->getUploadedFiles());
+        /** @var UploadedFileInterface|null $file */
+        $file = $file[0] ?? null;
 
 
-            $this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`, `published`) VALUES (?, ?, ?, ?, ?)', [
-                $user->id,
-                $code,
-                $file->getClientFilename(),
-                $storagePath,
-                $published,
-            ]);
+        if ($file === null) {
+            $this->json['message'] = 'Request without file attached.';
 
 
-            $json['message'] = 'OK';
-            $json['url'] = urlFor("/{$user->user_code}/{$code}.{$fileInfo['extension']}");
+            throw new ValidationException(json($response, $this->json, 400));
+        }
 
 
-            $this->logger->info("User $user->username uploaded new media.", [$this->database->getPdo()->lastInsertId()]);
+        if ($file->getError() === UPLOAD_ERR_INI_SIZE) {
+            $this->json['message'] = 'File too large (upload_max_filesize too low?).';
 
 
-            return json($response, $json, 201);
-        } catch (Exception $e) {
-            $this->updateUserQuota($request, $user->id, $file->getSize(), true);
-            throw $e;
+            throw new ValidationException(json($response, $this->json, 400));
+        }
+
+        return $file;
+    }
+
+    /**
+     * @param  Request  $request
+     * @param  Response  $response
+     * @param  UploadedFileInterface  $file
+     * @param $user
+     * @return void
+     * @throws ValidationException
+     */
+    protected function validateUser(Request $request, Response $response, UploadedFileInterface $file, $user)
+    {
+        if (!$user->active) {
+            $this->json['message'] = 'Account disabled.';
+
+            throw new ValidationException(json($response, $this->json, 401));
         }
         }
     }
     }
+
+
+    /**
+     * @param  Request  $request
+     * @param  Response  $response
+     * @param  UploadedFileInterface  $file
+     * @param $user
+     * @return Response
+     * @throws \League\Flysystem\FileExistsException
+     */
+    protected function saveMedia(Response $response, UploadedFileInterface $file, $user)
+    {
+        do {
+            $code = humanRandomString();
+        } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
+
+        $published = 1;
+        if ($this->getSetting('hide_by_default') === 'on') {
+            $published = 0;
+        }
+
+        $fileInfo = pathinfo($file->getClientFilename());
+        $storagePath = "$user->user_code/$code.$fileInfo[extension]";
+
+        $this->storage->writeStream($storagePath, $file->getStream()->detach());
+
+        $this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`, `published`) VALUES (?, ?, ?, ?, ?)', [
+            $user->id,
+            $code,
+            $file->getClientFilename(),
+            $storagePath,
+            $published,
+        ]);
+
+        $this->json['message'] = 'OK';
+        $this->json['url'] = urlFor("/{$user->user_code}/{$code}.{$fileInfo['extension']}");
+
+        $this->logger->info("User $user->username uploaded new media.", [$this->database->getPdo()->lastInsertId()]);
+
+        return json($response, $this->json, 201);
+    }
 }
 }

+ 9 - 4
app/Controllers/UserController.php

@@ -98,14 +98,14 @@ class UserController extends Controller
 
 
         $maxUserQuota = -1;
         $maxUserQuota = -1;
         if ($this->getSetting('quota_enabled') === 'on') {
         if ($this->getSetting('quota_enabled') === 'on') {
-            $maxUserQuota = param($request, 'max_user_quota', humanFileSize($this->getSetting('default_user_quota'), 0, true));
-            if (!preg_match('/([0-9]+[K|M|G|T])|(\-1)/i', $maxUserQuota)) {
+            $maxUserQuotaStr = param($request, 'max_user_quota', humanFileSize($this->getSetting('default_user_quota'), 0, true));
+            if (!preg_match('/([0-9]+[K|M|G|T])|(\-1)/i', $maxUserQuotaStr)) {
                 $this->session->alert(lang('invalid_quota', 'danger'));
                 $this->session->alert(lang('invalid_quota', 'danger'));
                 return redirect($response, route('user.create'));
                 return redirect($response, route('user.create'));
             }
             }
 
 
-            if ($maxUserQuota !== '-1') {
-                $maxUserQuota = stringToBytes($maxUserQuota);
+            if ($maxUserQuotaStr !== '-1') {
+                $maxUserQuota = stringToBytes($maxUserQuotaStr);
             }
             }
         }
         }
 
 
@@ -201,6 +201,7 @@ class UserController extends Controller
             return redirect($response, route('user.edit', ['id' => $id]));
             return redirect($response, route('user.edit', ['id' => $id]));
         }
         }
 
 
+        $user->max_disk_quota = -1;
         if ($this->getSetting('quota_enabled') === 'on') {
         if ($this->getSetting('quota_enabled') === 'on') {
             $maxUserQuota = param($request, 'max_user_quota', humanFileSize($this->getSetting('default_user_quota'), 0, true));
             $maxUserQuota = param($request, 'max_user_quota', humanFileSize($this->getSetting('default_user_quota'), 0, true));
             if (!preg_match('/([0-9]+[K|M|G|T])|(\-1)/i', $maxUserQuota)) {
             if (!preg_match('/([0-9]+[K|M|G|T])|(\-1)/i', $maxUserQuota)) {
@@ -234,6 +235,10 @@ class UserController extends Controller
             ]);
             ]);
         }
         }
 
 
+        if ($user->id === $this->session->get('user_id')) {
+            $this->setSessionQuotaInfo($user->current_disk_quota, $user->max_disk_quota);
+        }
+
         $this->session->alert(lang('user_updated', [param($request, 'username')]), 'success');
         $this->session->alert(lang('user_updated', [param($request, 'username')]), 'success');
         $this->logger->info('User '.$this->session->get('username')." updated $user->id.", [
         $this->logger->info('User '.$this->session->get('username')." updated $user->id.", [
             array_diff_key((array) $user, array_flip(['password'])),
             array_diff_key((array) $user, array_flip(['password'])),

+ 32 - 0
app/Exceptions/ValidationException.php

@@ -0,0 +1,32 @@
+<?php
+
+
+namespace App\Exceptions;
+
+
+use Exception;
+use Psr\Http\Message\ResponseInterface as Response;
+use Throwable;
+
+class ValidationException extends Exception
+{
+    /**
+     * @var Response
+     */
+    private $response;
+
+    public function __construct(Response $response, $message = "", $code = 0, Throwable $previous = null)
+    {
+        parent::__construct($message, $response->getStatusCode(), $previous);
+        $this->response = $response;
+    }
+
+    /**
+     * @return Response
+     */
+    public function response(): Response
+    {
+        return $this->response;
+    }
+
+}

+ 3 - 2
app/routes.php

@@ -22,7 +22,8 @@ use Slim\Routing\RouteCollectorProxy;
 global $app;
 global $app;
 $app->group('', function (RouteCollectorProxy $group) {
 $app->group('', function (RouteCollectorProxy $group) {
     $group->get('/home[/page/{page}]', [DashboardController::class, 'home'])->setName('home');
     $group->get('/home[/page/{page}]', [DashboardController::class, 'home'])->setName('home');
-    $group->get('/upload', [UploadController::class, 'webUpload'])->setName('upload.web');
+    $group->get('/upload', [UploadController::class, 'uploadWebPage'])->setName('upload.web.show');
+    $group->post('/upload/web', [UploadController::class, 'uploadWeb'])->setName('upload.web');
 
 
     $group->group('', function (RouteCollectorProxy $group) {
     $group->group('', function (RouteCollectorProxy $group) {
         $group->get('/home/switchView', [DashboardController::class, 'switchView'])->setName('switchView');
         $group->get('/home/switchView', [DashboardController::class, 'switchView'])->setName('switchView');
@@ -76,7 +77,7 @@ $app->get('/login', [LoginController::class, 'show'])->setName('login.show');
 $app->post('/login', [LoginController::class, 'login'])->setName('login');
 $app->post('/login', [LoginController::class, 'login'])->setName('login');
 $app->map(['GET', 'POST'], '/logout', [LoginController::class, 'logout'])->setName('logout');
 $app->map(['GET', 'POST'], '/logout', [LoginController::class, 'logout'])->setName('logout');
 
 
-$app->post('/upload', [UploadController::class, 'upload'])->setName('upload');
+$app->post('/upload', [UploadController::class, 'uploadEndpoint'])->setName('upload');
 
 
 $app->get('/{userCode}/{mediaCode}', [MediaController::class, 'show'])->setName('public');
 $app->get('/{userCode}/{mediaCode}', [MediaController::class, 'show'])->setName('public');
 $app->get('/{userCode}/{mediaCode}/delete/{token}', [MediaController::class, 'show'])->setName('public.delete.show')->add(CheckForMaintenanceMiddleware::class);
 $app->get('/{userCode}/{mediaCode}/delete/{token}', [MediaController::class, 'show'])->setName('public.delete.show')->add(CheckForMaintenanceMiddleware::class);

+ 1 - 1
resources/templates/comp/navbar.twig

@@ -12,7 +12,7 @@
                     </a>
                     </a>
                 </li>
                 </li>
                 <li class="nav-item">
                 <li class="nav-item">
-                    <a href="{{ route('upload') }}" class="nav-link {{ inPath(request.uri.path, '/upload') ? 'active' }}"><i class="fas fa-fw fa-upload"></i>
+                    <a href="{{ route('upload.web.show') }}" class="nav-link {{ inPath(request.uri.path, '/upload') ? 'active' }}"><i class="fas fa-fw fa-upload"></i>
                         {{ lang('upload') }}
                         {{ lang('upload') }}
                     </a>
                     </a>
                 </li>
                 </li>

+ 1 - 2
resources/templates/upload/web.twig

@@ -20,11 +20,10 @@
                 </div>
                 </div>
                 <div class="row">
                 <div class="row">
                     <div class="col">
                     <div class="col">
-                        <form action="{{ route('upload') }}?web=1" method="post" id="upload-dropzone" class="dropzone">
+                        <form action="{{ route('upload.web') }}" method="post" id="upload-dropzone" class="dropzone">
                             <div class="fallback">
                             <div class="fallback">
                                 <input name="file" type="file" multiple>
                                 <input name="file" type="file" multiple>
                             </div>
                             </div>
-                            <input type="hidden" name="token" value="{{ user.token }}">
                         </form>
                         </form>
                     </div>
                     </div>
                 </div>
                 </div>