Browse Source

Implemented validation helper
Dependencies resolved through container

Sergio Brighenti 5 years ago
parent
commit
db483bb53e

+ 0 - 1
app/Controllers/AdminController.php

@@ -18,7 +18,6 @@ class AdminController extends Controller
      * @throws \Twig\Error\RuntimeError
      * @throws \Twig\Error\SyntaxError
      *
-     * @throws FileNotFoundException
      */
     public function system(Request $request, Response $response): Response
     {

+ 17 - 10
app/Controllers/Auth/PasswordRecoveryController.php

@@ -5,6 +5,7 @@ namespace App\Controllers\Auth;
 
 use App\Controllers\Controller;
 use App\Web\Mail;
+use App\Web\ValidationChecker;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
 use Slim\Exception\HttpNotFoundException;
@@ -53,7 +54,7 @@ class PasswordRecoveryController extends Controller
         ]);
 
         Mail::make()
-            ->from('no-reply@'.str_ireplace('www.', '', parse_url($this->config['base_url'], PHP_URL_HOST)), $this->config['app_name'])
+            ->from(platform_mail(), $this->config['app_name'])
             ->to(param($request, 'email'))
             ->subject(lang('mail.recover_password', [$this->config['app_name']]))
             ->message(lang('mail.recover_text', [
@@ -104,15 +105,21 @@ class PasswordRecoveryController extends Controller
             throw new HttpNotFoundException($request);
         }
 
-        if (param($request, 'password') === null) {
-            $this->session->alert(lang('password_required'), 'danger');
-
-            return redirect($response, route('recover.password', ['resetToken' => $resetToken]));
-        }
-
-        if (param($request, 'password') !== param($request, 'password_repeat')) {
-            $this->session->alert(lang('password_match'), 'danger');
-
+        $validator = ValidationChecker::make()
+            ->rules([
+                'password.required' => !empty(param($request, 'password')),
+                'password.match' => param($request, 'password') === param($request, 'password_repeat'),
+            ])
+            ->onFail(function ($rule) {
+                $alerts = [
+                    'password.required' => lang('password_required'),
+                    'password.match' => lang('password_match'),
+                ];
+
+                $this->session->alert($alerts[$rule], 'danger');
+            });
+
+        if ($validator->fails()){
             return redirect($response, route('recover.password', ['resetToken' => $resetToken]));
         }
 

+ 10 - 31
app/Controllers/Auth/RegisterController.php

@@ -3,9 +3,8 @@
 
 namespace App\Controllers\Auth;
 
-use App\Controllers\Common\ValidateUser;
 use App\Controllers\Controller;
-use App\Exceptions\ValidationException;
+use App\Database\Queries\UserQuery;
 use App\Web\Mail;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
@@ -13,7 +12,6 @@ use Slim\Exception\HttpNotFoundException;
 
 class RegisterController extends Controller
 {
-    use ValidateUser;
 
     /**
      * @param  Request  $request
@@ -54,44 +52,26 @@ class RegisterController extends Controller
             throw new HttpNotFoundException($request);
         }
 
-        try {
-            $this->validateUser($request, $response, route('register.show'));
-        } catch (ValidationException $e) {
-            return $e->response();
-        }
-
-        if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ?', param($request, 'email'))->fetch()->count > 0) {
-            $this->session->alert(lang('email_taken'), 'danger');
+        $validator = $this->getUserCreateValidator($request);
 
+        if ($validator->fails()) {
             return redirect($response, route('register.show'));
         }
 
-        if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', param($request, 'username'))->fetch()->count > 0) {
-            $this->session->alert(lang('username_taken'), 'danger');
-
-            return redirect($response, route('register.show'));
-        }
-
-        do {
-            $userCode = humanRandomString(5);
-        } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
-
-        $token = $this->generateUserUploadToken();
         $activateToken = bin2hex(random_bytes(16));
 
-        $this->database->query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`, `activate_token`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
+        make(UserQuery::class)->create(
             param($request, 'email'),
             param($request, 'username'),
-            password_hash(param($request, 'password'), PASSWORD_DEFAULT),
+            param($request, 'password'),
             0,
             0,
-            $userCode,
-            $token,
-            $activateToken,
-        ]);
+            (int) $this->getSetting('default_user_quota', -1),
+            $activateToken
+        );
 
         Mail::make()
-            ->from('no-reply@'.str_ireplace('www.', '', parse_url($this->config['base_url'], PHP_URL_HOST)), $this->config['app_name'])
+            ->from(platform_mail(), $this->config['app_name'])
             ->to(param($request, 'email'))
             ->subject(lang('mail.activate_account', [$this->config['app_name']]))
             ->message(lang('mail.activate_text', [
@@ -109,12 +89,11 @@ class RegisterController extends Controller
     }
 
     /**
-     * @param  Request  $request
      * @param  Response  $response
      * @param  string  $activateToken
      * @return Response
      */
-    public function activateUser(Request $request, Response $response, string $activateToken): Response
+    public function activateUser(Response $response, string $activateToken): Response
     {
         if ($this->session->get('logged', false)) {
             return redirect($response, route('home'));

+ 10 - 17
app/Controllers/ClientController.php

@@ -2,26 +2,22 @@
 
 namespace App\Controllers;
 
+use App\Database\Queries\UserQuery;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
-use Slim\Exception\HttpNotFoundException;
-use Slim\Exception\HttpUnauthorizedException;
 
 class ClientController extends Controller
 {
     /**
-     * @param Request  $request
-     * @param Response $response
-     * @param int      $id
-     *
-     * @throws HttpNotFoundException
-     * @throws HttpUnauthorizedException
+     * @param  Request  $request
+     * @param  Response  $response
+     * @param  int  $id
      *
      * @return Response
      */
     public function getShareXConfig(Request $request, Response $response, int $id): Response
     {
-        $user = $this->getUser($request, $id, true);
+        $user = make(UserQuery::class)->get($request, $id, true);
 
         if ($user->token === null || $user->token === '') {
             $this->session->alert(lang('no_upload_token'), 'danger');
@@ -48,21 +44,18 @@ class ClientController extends Controller
     }
 
     /**
-     * @param Request  $request
-     * @param Response $response
-     * @param int      $id
+     * @param  Request  $request
+     * @param  Response  $response
+     * @param  int  $id
      *
-     * @throws HttpNotFoundException
-     * @throws HttpUnauthorizedException
+     * @return Response
      * @throws \Twig\Error\LoaderError
      * @throws \Twig\Error\RuntimeError
      * @throws \Twig\Error\SyntaxError
-     *
-     * @return Response
      */
     public function getBashScript(Request $request, Response $response, int $id): Response
     {
-        $user = $this->getUser($request, $id, true);
+        $user = make(UserQuery::class)->get($request, $id, true);
 
         if ($user->token === null || $user->token === '') {
             $this->session->alert(lang('no_upload_token'), 'danger');

+ 27 - 39
app/Controllers/Controller.php

@@ -3,17 +3,18 @@
 namespace App\Controllers;
 
 use App\Database\DB;
+use App\Database\Queries\UserQuery;
 use App\Web\Lang;
 use App\Web\Session;
+use App\Web\ValidationChecker;
 use App\Web\View;
 use DI\Container;
 use DI\DependencyException;
 use DI\NotFoundException;
+use Exception;
 use League\Flysystem\Filesystem;
 use Monolog\Logger;
 use Psr\Http\Message\ServerRequestInterface as Request;
-use Slim\Exception\HttpNotFoundException;
-use Slim\Exception\HttpUnauthorizedException;
 
 /**
  * @property Session session
@@ -88,12 +89,10 @@ abstract class Controller
      * @param $fileSize
      * @param  bool  $dec
      * @return bool
-     * @throws HttpNotFoundException
-     * @throws HttpUnauthorizedException
      */
     protected function updateUserQuota(Request $request, $userId, $fileSize, $dec = false)
     {
-        $user = $this->getUser($request, $userId);
+        $user = make(UserQuery::class)->get($request, $userId);
 
         if ($dec) {
             $tot = max($user->current_disk_quota - $fileSize, 0);
@@ -113,34 +112,9 @@ abstract class Controller
         return true;
     }
 
-    /**
-     * @param  Request  $request
-     * @param $id
-     * @param  bool  $authorize
-     *
-     * @return mixed
-     * @throws HttpUnauthorizedException
-     *
-     * @throws HttpNotFoundException
-     */
-    protected function getUser(Request $request, $id, $authorize = false)
-    {
-        $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
-
-        if (!$user) {
-            throw new HttpNotFoundException($request);
-        }
-
-        if ($authorize && $user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) {
-            throw new HttpUnauthorizedException($request);
-        }
-
-        return $user;
-    }
-
     /**
      * @param $userId
-     * @throws \Exception
+     * @throws Exception
      */
     protected function refreshRememberCookie($userId)
     {
@@ -167,16 +141,30 @@ abstract class Controller
         }
     }
 
-
     /**
-     * @return string
+     * @param  Request  $request
+     * @return ValidationChecker
      */
-    protected function generateUserUploadToken(): string
+    public function getUserCreateValidator(Request $request)
     {
-        do {
-            $token = 'token_'.md5(uniqid('', true));
-        } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0);
-
-        return $token;
+        return ValidationChecker::make()
+            ->rules([
+                'email.required' => filter_var(param($request, 'email'), FILTER_VALIDATE_EMAIL) !== false,
+                'username.required' => !empty(param($request, 'username')),
+                'password.required' => !empty(param($request, 'password')),
+                'email.unique' => $this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ?', param($request, 'email'))->fetch()->count == 0,
+                'username.unique' => $this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', param($request, 'username'))->fetch()->count == 0,
+            ])
+            ->onFail(function ($rule) {
+                $alerts = [
+                    'email.required' => lang('email_required'),
+                    'username.required' => lang('username_required'),
+                    'password.required' => lang('password_required'),
+                    'email.unique' => lang('email_taken'),
+                    'username.unique' => lang('username_taken'),
+                ];
+
+                $this->session->alert($alerts[$rule], 'danger');
+            });
     }
 }

+ 3 - 3
app/Controllers/DashboardController.php

@@ -38,8 +38,6 @@ class DashboardController extends Controller
     {
         $page = max(0, --$page);
 
-        $query = new MediaQuery($this->database, $this->session->get('admin', false), $this->storage);
-
         switch (param($request, 'sort', 'time')) {
             case 'size':
                 $order = MediaQuery::ORDER_SIZE;
@@ -53,7 +51,9 @@ class DashboardController extends Controller
                 break;
         }
 
-        $query->orderBy($order, param($request, 'order', 'DESC'))
+        /** @var MediaQuery $query */
+        $query = make(MediaQuery::class, ['isAdmin' => (bool) $this->session->get('admin', false)])
+            ->orderBy($order, param($request, 'order', 'DESC'))
             ->withUserId($this->session->get('user_id'))
             ->search(param($request, 'search', null))
             ->run($page);

+ 2 - 3
app/Controllers/ExportController.php

@@ -3,6 +3,7 @@
 
 namespace App\Controllers;
 
+use App\Database\Queries\UserQuery;
 use League\Flysystem\FileNotFoundException;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
@@ -16,13 +17,11 @@ class ExportController extends Controller
      * @param  Response  $response
      * @param  int|null  $id
      * @return Response
-     * @throws \Slim\Exception\HttpNotFoundException
-     * @throws \Slim\Exception\HttpUnauthorizedException
      * @throws \ZipStream\Exception\OverflowException
      */
     public function downloadData(Request $request, Response $response, int $id): Response
     {
-        $user = $this->getUser($request, $id, true);
+        $user = make(UserQuery::class)->get($request, $id, true);
 
         $medias = $this->database->query('SELECT `uploads`.`filename`, `uploads`.`storage_path` FROM `uploads` WHERE `user_id` = ?', $user->id);
 

+ 2 - 1
app/Controllers/MediaController.php

@@ -2,6 +2,7 @@
 
 namespace App\Controllers;
 
+use App\Database\Queries\UserQuery;
 use GuzzleHttp\Psr7\Stream;
 use Intervention\Image\Constraint;
 use Intervention\Image\ImageManagerStatic as Image;
@@ -202,7 +203,7 @@ class MediaController extends Controller
             $this->updateUserQuota($request, $media->user_id, $size, true);
             $this->logger->info('User '.$this->session->get('username').' deleted a media.', [$id]);
             if ($media->user_id === $this->session->get('user_id')) {
-                $user = $this->getUser($request, $media->user_id, true);
+                $user = make(UserQuery::class)->get($request, $id, true);
                 $this->setSessionQuotaInfo($user->current_disk_quota, $user->max_disk_quota);
             }
         } else {

+ 23 - 23
app/Controllers/ProfileController.php

@@ -3,28 +3,25 @@
 
 namespace App\Controllers;
 
+use App\Database\Queries\UserQuery;
+use App\Web\ValidationChecker;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
-use Slim\Exception\HttpNotFoundException;
-use Slim\Exception\HttpUnauthorizedException;
 
 class ProfileController extends Controller
 {
     /**
-     * @param Request  $request
-     * @param Response $response
+     * @param  Request  $request
+     * @param  Response  $response
      *
-     * @throws HttpNotFoundException
-     * @throws HttpUnauthorizedException
+     * @return Response
      * @throws \Twig\Error\LoaderError
      * @throws \Twig\Error\RuntimeError
      * @throws \Twig\Error\SyntaxError
-     *
-     * @return Response
      */
     public function profile(Request $request, Response $response): Response
     {
-        $user = $this->getUser($request, $this->session->get('user_id'), true);
+        $user = make(UserQuery::class)->get($request, $this->session->get('user_id'), true);
 
         return view()->render($response, 'user/edit.twig', [
             'profile' => true,
@@ -33,28 +30,31 @@ class ProfileController extends Controller
     }
 
     /**
-     * @param Request  $request
-     * @param Response $response
-     * @param int      $id
-     *
-     * @throws HttpNotFoundException
-     * @throws HttpUnauthorizedException
+     * @param  Request  $request
+     * @param  Response  $response
+     * @param  int  $id
      *
      * @return Response
      */
     public function profileEdit(Request $request, Response $response, int $id): Response
     {
-        if (param($request, 'email') === null) {
-            $this->session->alert(lang('email_required'), 'danger');
-
-            return redirect($response, route('profile'));
-        }
+        $user = make(UserQuery::class)->get($request, $id, true);
 
-        $user = $this->getUser($request, $id, true);
+        $validator = ValidationChecker::make()
+            ->rules([
+                'email.required' => filter_var(param($request, 'email'), FILTER_VALIDATE_EMAIL),
+                'email.unique' => $this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ? AND `email` <> ?', [param($request, 'email'), $user->email])->fetch()->count == 0,
+            ])
+            ->onFail(function ($rule) {
+                $alerts = [
+                    'email.required' => lang('email_required'),
+                    'email.unique' => lang('email_taken'),
+                ];
 
-        if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ? AND `email` <> ?', [param($request, 'email'), $user->email])->fetch()->count > 0) {
-            $this->session->alert(lang('email_taken'), 'danger');
+                $this->session->alert($alerts[$rule], 'danger');
+            });
 
+        if ($validator->fails()) {
             return redirect($response, route('profile'));
         }
 

+ 2 - 3
app/Controllers/SettingController.php

@@ -3,6 +3,7 @@
 
 namespace App\Controllers;
 
+use App\Database\Queries\UserQuery;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
 
@@ -13,8 +14,6 @@ class SettingController extends Controller
      * @param  Response  $response
      *
      * @return Response
-     * @throws \Slim\Exception\HttpNotFoundException
-     * @throws \Slim\Exception\HttpUnauthorizedException
      */
     public function saveSettings(Request $request, Response $response): Response
     {
@@ -27,7 +26,7 @@ class SettingController extends Controller
         $this->updateSetting('hide_by_default', param($request, 'hide_by_default', 'off'));
         $this->updateSetting('quota_enabled', param($request, 'quota_enabled', 'off'));
 
-        $user = $this->getUser($request, $this->session->get('user_id'));
+        $user = make(UserQuery::class)->get($request, $this->session->get('user_id'));
         $this->setSessionQuotaInfo($user->current_disk_quota, $user->max_disk_quota);
 
         $this->updateSetting('default_user_quota', stringToBytes(param($request, 'default_user_quota', '1G')));

+ 2 - 6
app/Controllers/UploadController.php

@@ -2,6 +2,7 @@
 
 namespace App\Controllers;
 
+use App\Database\Queries\UserQuery;
 use App\Exceptions\ValidationException;
 use Exception;
 use Psr\Http\Message\ResponseInterface as Response;
@@ -32,8 +33,6 @@ class UploadController extends Controller
      * @param  Request  $request
      * @param  Response  $response
      * @return Response
-     * @throws \Slim\Exception\HttpNotFoundException
-     * @throws \Slim\Exception\HttpUnauthorizedException
      * @throws Exception
      */
     public function uploadWeb(Request $request, Response $response): Response
@@ -47,7 +46,7 @@ class UploadController extends Controller
         try {
             $file = $this->validateFile($request, $response);
 
-            $user = $this->getUser($request, $this->session->get('user_id'));
+            $user = make(UserQuery::class)->get($request, $this->session->get('user_id'));
 
             $this->validateUser($request, $response, $file, $user);
         } catch (ValidationException $e) {
@@ -76,8 +75,6 @@ class UploadController extends Controller
      * @param  Response  $response
      *
      * @return Response
-     * @throws \Slim\Exception\HttpNotFoundException
-     * @throws \Slim\Exception\HttpUnauthorizedException
      * @throws Exception
      */
     public function uploadEndpoint(Request $request, Response $response): Response
@@ -181,7 +178,6 @@ class UploadController extends Controller
 
 
     /**
-     * @param  Request  $request
      * @param  Response  $response
      * @param  UploadedFileInterface  $file
      * @param $user

+ 47 - 99
app/Controllers/UserController.php

@@ -2,16 +2,13 @@
 
 namespace App\Controllers;
 
-use App\Exceptions\ValidationException;
-use App\Validators\ValidateUser;
+use App\Database\Queries\UserQuery;
+use App\Web\ValidationChecker;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
-use Slim\Exception\HttpNotFoundException;
-use Slim\Exception\HttpUnauthorizedException;
 
 class UserController extends Controller
 {
-    use ValidateUser;
 
     const PER_PAGE = 15;
 
@@ -70,27 +67,15 @@ class UserController extends Controller
      */
     public function store(Request $request, Response $response): Response
     {
-        try {
-            $this->validateUser($request, $response, route('user.create'));
-        } catch (ValidationException $e) {
-            return $e->response();
-        }
-
-        if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ?', param($request, 'email'))->fetch()->count > 0) {
-            $this->session->alert(lang('email_taken'), 'danger');
-
-            return redirect($response, route('user.create'));
-        }
-
-        if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', param($request, 'username'))->fetch()->count > 0) {
-            $this->session->alert(lang('username_taken'), 'danger');
+        $validator = $this->getUserCreateValidator($request);
 
+        if ($validator->fails()) {
             return redirect($response, route('user.create'));
         }
 
         $maxUserQuota = -1;
         if ($this->getSetting('quota_enabled') === 'on') {
-            $maxUserQuotaStr = param($request, 'max_user_quota', humanFileSize($this->getSetting('default_user_quota'), 0, true));
+            $maxUserQuotaStr = param($request, 'max_user_quota', humanFileSize($this->getSetting('default_user_quota', -1), 0, true));
             if (!preg_match('/([0-9]+[K|M|G|T])|(\-1)/i', $maxUserQuotaStr)) {
                 $this->session->alert(lang('invalid_quota', 'danger'));
                 return redirect($response, route('user.create'));
@@ -101,22 +86,14 @@ class UserController extends Controller
             }
         }
 
-        do {
-            $userCode = humanRandomString(5);
-        } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
-
-        $token = $this->generateUserUploadToken();
-
-        $this->database->query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`, `max_disk_quota`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
+        make(UserQuery::class)->create(
             param($request, 'email'),
             param($request, 'username'),
-            password_hash(param($request, 'password'), PASSWORD_DEFAULT),
+            param($request, 'password'),
             param($request, 'is_admin') !== null ? 1 : 0,
             param($request, 'is_active') !== null ? 1 : 0,
-            $userCode,
-            $token,
-            $maxUserQuota,
-        ]);
+            $maxUserQuota
+        );
 
         $this->session->alert(lang('user_created', [param($request, 'username')]), 'success');
         $this->logger->info('User '.$this->session->get('username').' created a new user.', [array_diff_key($request->getParsedBody(), array_flip(['password']))]);
@@ -127,19 +104,16 @@ class UserController extends Controller
     /**
      * @param  Request  $request
      * @param  Response  $response
-     * @param $id
+     * @param  int  $id
      *
      * @return Response
      * @throws \Twig\Error\LoaderError
      * @throws \Twig\Error\RuntimeError
      * @throws \Twig\Error\SyntaxError
-     * @throws HttpUnauthorizedException
-     *
-     * @throws HttpNotFoundException
      */
     public function edit(Request $request, Response $response, int $id): Response
     {
-        $user = $this->getUser($request, $id, false);
+        $user = make(UserQuery::class)->get($request, $id);
 
         return view()->render($response, 'user/edit.twig', [
             'profile' => false,
@@ -155,35 +129,32 @@ class UserController extends Controller
      * @param  int  $id
      *
      * @return Response
-     * @throws HttpUnauthorizedException
-     *
-     * @throws HttpNotFoundException
      */
     public function update(Request $request, Response $response, int $id): Response
     {
-        try {
-            $this->validateUser($request, $response, route('user.edit', ['id' => $id]));
-        } catch (ValidationException $e) {
-            return $e->response();
-        }
-
-        $user = $this->getUser($request, $id, false);
-
-        if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ? AND `email` <> ?', [param($request, 'email'), $user->email])->fetch()->count > 0) {
-            $this->session->alert(lang('email_taken'), 'danger');
-
-            return redirect($response, route('user.edit', ['id' => $id]));
-        }
-
-        if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [param($request, 'username'), $user->username])->fetch()->count > 0) {
-            $this->session->alert(lang('username_taken'), 'danger');
-
-            return redirect($response, route('user.edit', ['id' => $id]));
-        }
-
-        if ($user->id === $this->session->get('user_id') && param($request, 'is_admin') === null) {
-            $this->session->alert(lang('cannot_demote'), 'danger');
-
+        $user = make(UserQuery::class)->get($request, $id);
+
+        $validator = ValidationChecker::make()
+            ->rules([
+                'email.required' => filter_var(param($request, 'email'), FILTER_VALIDATE_EMAIL),
+                'username.required' => !empty(param($request, 'username')),
+                'email.unique' => $this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ? AND `email` <> ?', [param($request, 'email'), $user->email])->fetch()->count == 0,
+                'username.unique' => $this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [param($request, 'username'), $user->username])->fetch()->count == 0,
+                'demote' => !($user->id === $this->session->get('user_id') && param($request, 'is_admin') === null),
+            ])
+            ->onFail(function ($rule) {
+                $alerts = [
+                    'email.required' => lang('email_required'),
+                    'username.required' => lang('username_required'),
+                    'email.unique' => lang('email_taken'),
+                    'username.unique' => lang('username_taken'),
+                    'demote' => lang('cannot_demote'),
+                ];
+
+                $this->session->alert($alerts[$rule], 'danger');
+            });
+
+        if ($validator->fails()) {
             return redirect($response, route('user.edit', ['id' => $id]));
         }
 
@@ -200,26 +171,15 @@ class UserController extends Controller
             }
         }
 
-        if (param($request, 'password') !== null && !empty(param($request, 'password'))) {
-            $this->database->query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=?, `max_disk_quota`=? WHERE `id` = ?', [
-                param($request, 'email'),
-                param($request, 'username'),
-                password_hash(param($request, 'password'), PASSWORD_DEFAULT),
-                param($request, 'is_admin') !== null ? 1 : 0,
-                param($request, 'is_active') !== null ? 1 : 0,
-                $user->max_disk_quota,
-                $user->id,
-            ]);
-        } else {
-            $this->database->query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=?, `max_disk_quota`=? WHERE `id` = ?', [
-                param($request, 'email'),
-                param($request, 'username'),
-                param($request, 'is_admin') !== null ? 1 : 0,
-                param($request, 'is_active') !== null ? 1 : 0,
-                $user->max_disk_quota,
-                $user->id,
-            ]);
-        }
+        make(UserQuery::class)->update(
+            $user->id,
+            param($request, 'email'),
+            param($request, 'username'),
+            param($request, 'password'),
+            param($request, 'is_admin') !== null ? 1 : 0,
+            param($request, 'is_active') !== null ? 1 : 0,
+            $user->max_disk_quota
+        );
 
         if ($user->id === $this->session->get('user_id')) {
             $this->setSessionQuotaInfo($user->current_disk_quota, $user->max_disk_quota);
@@ -240,13 +200,10 @@ class UserController extends Controller
      * @param  int  $id
      *
      * @return Response
-     * @throws HttpUnauthorizedException
-     *
-     * @throws HttpNotFoundException
      */
     public function delete(Request $request, Response $response, int $id): Response
     {
-        $user = $this->getUser($request, $id, false);
+        $user = make(UserQuery::class)->get($request, $id);
 
         if ($user->id === $this->session->get('user_id')) {
             $this->session->alert(lang('cannot_delete'), 'danger');
@@ -268,24 +225,15 @@ class UserController extends Controller
      * @param  int  $id
      *
      * @return Response
-     * @throws HttpUnauthorizedException
-     *
-     * @throws HttpNotFoundException
      */
     public function refreshToken(Request $request, Response $response, int $id): Response
     {
-        $user = $this->getUser($request, $id, true);
-
-        $token = $this->generateUserUploadToken();
-
-        $this->database->query('UPDATE `users` SET `token`=? WHERE `id` = ?', [
-            $token,
-            $user->id,
-        ]);
+        $query = make(UserQuery::class);
+        $user = $query->get($request, $id, true);
 
         $this->logger->info('User '.$this->session->get('username')." refreshed token of user $user->id.");
 
-        $response->getBody()->write($token);
+        $response->getBody()->write($query->refreshToken($user->id));
 
         return $response;
     }

+ 23 - 9
app/Database/Queries/MediaQuery.php

@@ -42,17 +42,28 @@ class MediaQuery
     /**
      * MediaQuery constructor.
      *
-     * @param DB         $db
-     * @param bool       $isAdmin
-     * @param Filesystem $storage
+     * @param  DB  $db
+     * @param  bool  $isAdmin
+     * @param  Filesystem  $storage
      */
-    public function __construct(DB $db, bool $isAdmin, Filesystem $storage)
+    public function __construct(DB $db, Filesystem $storage, bool $isAdmin)
     {
         $this->db = $db;
         $this->isAdmin = $isAdmin;
         $this->storage = $storage;
     }
 
+    /**
+     * @param  DB  $db
+     * @param  bool  $isAdmin
+     * @param  Filesystem  $storage
+     * @return MediaQuery
+     */
+    public static function make(DB $db, Filesystem $storage, bool $isAdmin)
+    {
+        return new self($db, $storage, $isAdmin);
+    }
+
     /**
      * @param $id
      *
@@ -66,8 +77,8 @@ class MediaQuery
     }
 
     /**
-     * @param string|null $type
-     * @param string      $mode
+     * @param  string|null  $type
+     * @param  string  $mode
      *
      * @return $this
      */
@@ -80,7 +91,7 @@ class MediaQuery
     }
 
     /**
-     * @param string $text
+     * @param  string  $text
      *
      * @return $this
      */
@@ -92,7 +103,8 @@ class MediaQuery
     }
 
     /**
-     * @param int $page
+     * @param  int  $page
+     * @return MediaQuery|void
      */
     public function run(int $page)
     {
@@ -150,10 +162,12 @@ class MediaQuery
             }
             $media->extension = pathinfo($media->filename, PATHINFO_EXTENSION);
         }
+
+        return $this;
     }
 
     /**
-     * @param int $page
+     * @param  int  $page
      */
     private function runWithOrderBySize(int $page)
     {

+ 147 - 0
app/Database/Queries/UserQuery.php

@@ -0,0 +1,147 @@
+<?php
+
+
+namespace App\Database\Queries;
+
+
+use App\Database\DB;
+use App\Web\Session;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpUnauthorizedException;
+
+class UserQuery
+{
+    /**
+     * @var DB
+     */
+    private $database;
+    /**
+     * @var Session
+     */
+    private $session;
+
+    /**
+     * UserQuery constructor.
+     * @param  DB  $db
+     * @param  Session|null  $session
+     */
+    public function __construct(DB $db, ?Session $session)
+    {
+        $this->database = $db;
+        $this->session = $session;
+    }
+
+    /**
+     * @param  DB  $db
+     * @param  Session|null  $session
+     * @return UserQuery
+     */
+    public static function make(DB $db, Session $session = null)
+    {
+        return new self($db, $session);
+    }
+
+    /**
+     * @param  Request  $request
+     * @param $id
+     * @param  bool  $authorize
+     * @return mixed
+     * @throws HttpNotFoundException
+     * @throws HttpUnauthorizedException
+     */
+    public function get(Request $request, $id, $authorize = false)
+    {
+        $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
+
+        if (!$user) {
+            throw new HttpNotFoundException($request);
+        }
+
+        if ($authorize) {
+            if ($this->session === null) {
+                throw new \InvalidArgumentException('The session is null.');
+            }
+
+            if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) {
+                throw new HttpUnauthorizedException($request);
+            }
+        }
+
+        return $user;
+    }
+
+
+    public function create(string $email, string $username, string $password, int $isAdmin = 0, int $isActive = 0, int $maxUserQuota = -1, string $activateToken = null)
+    {
+        do {
+            $userCode = humanRandomString(5);
+        } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
+
+        $token = $this->generateUserUploadToken();
+
+        return $this->database->query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`, `max_disk_quota`, `activate_token`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', [
+            $email,
+            $username,
+            password_hash($password, PASSWORD_DEFAULT),
+            $isAdmin,
+            $isActive,
+            $userCode,
+            $token,
+            $maxUserQuota,
+            $activateToken,
+        ]);
+    }
+
+    public function update($id, string $email, string $username, string $password = null, int $isAdmin = 0, int $isActive = 0, int $maxUserQuota = -1)
+    {
+        if (!empty($password)) {
+            $this->database->query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=?, `max_disk_quota`=? WHERE `id` = ?', [
+                $email,
+                $username,
+                password_hash($password, PASSWORD_DEFAULT),
+                $isAdmin,
+                $isActive,
+                $maxUserQuota,
+                $id,
+            ]);
+        } else {
+            $this->database->query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=?, `max_disk_quota`=? WHERE `id` = ?', [
+                $email,
+                $username,
+                $isAdmin,
+                $isActive,
+                $maxUserQuota,
+                $id,
+            ]);
+        }
+    }
+
+    /**
+     * @param $id
+     * @return string
+     */
+    public function refreshToken($id)
+    {
+        $token = $this->generateUserUploadToken();
+
+        $this->database->query('UPDATE `users` SET `token`=? WHERE `id` = ?', [
+            $token,
+            $id,
+        ]);
+
+        return $token;
+    }
+
+    /**
+     * @return string
+     */
+    protected function generateUserUploadToken(): string
+    {
+        do {
+            $token = 'token_'.md5(uniqid('', true));
+        } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0);
+
+        return $token;
+    }
+}

+ 0 - 44
app/Validators/ValidateUser.php

@@ -1,44 +0,0 @@
-<?php
-
-
-namespace App\Validators;
-
-use App\Exceptions\ValidationException;
-use Psr\Http\Message\ResponseInterface as Response;
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-trait ValidateUser
-{
-
-    /**
-     * Partially validate a manager user request
-     *
-     * @param  Request  $request
-     * @param  Response  $response
-     * @param $routeOnFail
-     * @return bool
-     * @throws ValidationException
-     */
-    protected function validateUser(Request $request, Response $response, $routeOnFail)
-    {
-        if (param($request, 'email') === null && !filter_var(param($request, 'email'), FILTER_VALIDATE_EMAIL)) {
-            $this->session->alert(lang('email_required'), 'danger');
-
-            throw new ValidationException(redirect($response, $routeOnFail));
-        }
-
-        if (param($request, 'username') === null) {
-            $this->session->alert(lang('username_required'), 'danger');
-
-            throw new ValidationException(redirect($response, $routeOnFail));
-        }
-
-        if (param($request, 'password') === null) {
-            $this->session->alert(lang('password_required'), 'danger');
-
-            throw new ValidationException(redirect($response, $routeOnFail));
-        }
-
-        return true;
-    }
-}

+ 55 - 0
app/Web/ValidationChecker.php

@@ -0,0 +1,55 @@
+<?php
+
+
+namespace App\Web;
+
+
+class ValidationChecker
+{
+    protected $rules = [];
+    protected $failClosure;
+
+    /**
+     * @return ValidationChecker
+     */
+    public static function make()
+    {
+        return new self();
+    }
+
+    /**
+     * @param  array  $rules
+     * @return $this
+     */
+    public function rules(array $rules)
+    {
+        $this->rules = $rules;
+        return $this;
+    }
+
+    /**
+     * @param  callable  $closure
+     * @return $this
+     */
+    public function onFail(callable $closure)
+    {
+        $this->failClosure = $closure;
+        return $this;
+    }
+
+    /**
+     * @return bool
+     */
+    public function fails()
+    {
+        foreach ($this->rules as $rule => $condition) {
+            if (!$condition) {
+                if (is_callable($this->failClosure)) {
+                    ($this->failClosure)($rule);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 29 - 0
app/helpers.php

@@ -157,6 +157,22 @@ if (!function_exists('resolve')) {
     }
 }
 
+if (!function_exists('make')) {
+    /**
+     * Resolve a service from de DI container.
+     *
+     * @param  string  $class
+     * @param  array  $params
+     * @return mixed
+     */
+    function make(string $class, array $params = [])
+    {
+        global $app;
+
+        return $app->getContainer()->make($class, $params);
+    }
+}
+
 if (!function_exists('view')) {
     /**
      * Render a view to the response body.
@@ -455,3 +471,16 @@ if (!function_exists('dsnFromConfig')) {
         return $config['db']['connection'].':'.$dsn;
     }
 }
+
+if (!function_exists('platform_mail')) {
+    /**
+     * Return the system no-reply mail.
+     *
+     * @param  string  $mailbox
+     * @return string
+     */
+    function platform_mail($mailbox = 'no-reply'): string
+    {
+        return $mailbox.'@'.str_ireplace('www.', '', parse_url(resolve('config')['base_url'], PHP_URL_HOST));
+    }
+}

+ 1 - 1
src/js/app.js

@@ -25,7 +25,7 @@ var app = {
         $('#themes').mousedown(app.loadThemes);
         $('.checkForUpdatesButton').click(app.checkForUpdates);
 
-        $('.alert').fadeTo(4000, 500).slideUp(500, function () {
+        $('.alert').fadeTo(10000, 500).slideUp(500, function () {
             $('.alert').slideUp(500);
         });