diff --git a/app/Controllers/Controller.php b/app/Controllers/Controller.php index f6a6452..45e13ae 100644 --- a/app/Controllers/Controller.php +++ b/app/Controllers/Controller.php @@ -3,52 +3,34 @@ namespace App\Controllers; -use App\Exceptions\AuthenticationException; -use App\Exceptions\UnauthorizedException; -use App\Web\Session; -use Flight; -use App\Database\DB; use League\Flysystem\Adapter\Local; use League\Flysystem\Filesystem; +use Slim\Container; +use Slim\Http\Request; +use Slim\Http\Response; abstract class Controller { - /** - * Check if the current user is logged in - * @throws AuthenticationException - */ - protected function checkLogin(): void + /** @var Container */ + protected $container; + + public function __construct(Container $container) { - if (!Session::get('logged', false)) { - Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); - throw new AuthenticationException(); - } - - if (!DB::query('SELECT `id`, `active` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->active) { - Session::alert('Your account is not active anymore.', 'danger'); - Session::set('logged', false); - Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); - throw new AuthenticationException(); - } - + $this->container = $container; } /** - * Check if the current user is an admin - * @throws AuthenticationException - * @throws UnauthorizedException + * @param $name + * @return mixed|null + * @throws \Interop\Container\Exception\ContainerException */ - protected function checkAdmin(): void + public function __get($name) { - $this->checkLogin(); - - if (!DB::query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->is_admin) { - Session::alert('Your account is not admin anymore.', 'danger'); - Session::set('admin', false); - Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); - throw new UnauthorizedException(); + if ($this->container->has($name)) { + return $this->container->get($name); } + return null; } @@ -71,20 +53,6 @@ abstract class Controller */ protected function getStorage(): Filesystem { - return new Filesystem(new Local(Flight::get('config')['storage_dir'])); - } - - /** - * Set http2 header for a resource if is supported - * @param string $url - * @param string $as - */ - protected function http2push(string $url, string $as = 'image'): void - { - if (Flight::request()->scheme === 'HTTP/2.0') { - $headers = isset(Flight::response()->headers()['Link']) ? Flight::response()->headers()['Link'] : []; - $headers[] = "<${url}>; rel=preload; as=${as}"; - Flight::response()->header('Link', $headers); - } + return new Filesystem(new Local($this->settings['storage_dir'])); } } \ No newline at end of file diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index 85c0d89..d0d3437 100644 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -3,37 +3,44 @@ namespace App\Controllers; -use App\Database\DB; -use App\Traits\SingletonController; use App\Web\Session; -use Flight; use League\Flysystem\FileNotFoundException; +use Slim\Http\Request; +use Slim\Http\Response; class DashboardController extends Controller { - use SingletonController; const PER_PAGE = 21; - const PER_PAGE_ADMIN = 50; + const PER_PAGE_ADMIN = 25; - public function redirects(): void + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function redirects(Request $request, Response $response): Response { - $this->checkLogin(); - Flight::redirect('/home'); + return $response->withRedirect('/home'); } - public function home($page = 1): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + */ + public function home(Request $request, Response $response, $args): Response { - $this->checkLogin(); - + $page = isset($args['page']) ? (int)$args['page'] : 0; $page = max(0, --$page); if (Session::get('admin', false)) { - $medias = DB::query('SELECT `uploads`.*, `users`.`user_code`, `users`.`username` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id` ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [self::PER_PAGE_ADMIN, $page * self::PER_PAGE_ADMIN])->fetchAll(); - $pages = DB::query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count / self::PER_PAGE_ADMIN; + $medias = $this->database->query('SELECT `uploads`.*, `users`.`user_code`, `users`.`username` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id` ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [self::PER_PAGE_ADMIN, $page * self::PER_PAGE_ADMIN])->fetchAll(); + $pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count / self::PER_PAGE_ADMIN; } else { - $medias = DB::query('SELECT `uploads`.*,`users`.`user_code`, `users`.`username` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_id` = ? ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [Session::get('user_id'), self::PER_PAGE, $page * self::PER_PAGE])->fetchAll(); - $pages = DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` = ?', Session::get('user_id'))->fetch()->count / self::PER_PAGE; + $medias = $this->database->query('SELECT `uploads`.*,`users`.`user_code`, `users`.`username` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_id` = ? ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [Session::get('user_id'), self::PER_PAGE, $page * self::PER_PAGE])->fetchAll(); + $pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` = ?', Session::get('user_id'))->fetch()->count / self::PER_PAGE; } $filesystem = $this->getStorage(); @@ -51,7 +58,8 @@ class DashboardController extends Controller $media->size = $this->humanFilesize($size); } - Flight::render( + return $this->view->render( + $response, Session::get('admin', false) ? 'dashboard/admin.twig' : 'dashboard/home.twig', [ 'medias' => $medias, @@ -62,15 +70,19 @@ class DashboardController extends Controller ); } - public function system() + /** + * @param Request $request + * @param Response $response + * @return Response + * @throws FileNotFoundException + */ + public function system(Request $request, Response $response): Response { - $this->checkAdmin(); + $usersCount = $this->database->query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count; + $mediasCount = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count; + $orphanFilesCount = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` IS NULL')->fetch()->count; - $usersCount = DB::query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count; - $mediasCount = DB::query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count; - $orphanFilesCount = DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` IS NULL')->fetch()->count; - - $medias = DB::query('SELECT `users`.`user_code`, `uploads`.`code`, `uploads`.`storage_path` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id`')->fetchAll(); + $medias = $this->database->query('SELECT `users`.`user_code`, `uploads`.`code`, `uploads`.`storage_path` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id`')->fetchAll(); $totalSize = 0; @@ -79,7 +91,9 @@ class DashboardController extends Controller $totalSize += $filesystem->getSize($media->storage_path); } - Flight::render('dashboard/system.twig', [ + return $this->view->render( + $response, + 'dashboard/system.twig', [ 'usersCount' => $usersCount, 'mediasCount' => $mediasCount, 'orphanFilesCount' => $orphanFilesCount, diff --git a/app/Controllers/LoginController.php b/app/Controllers/LoginController.php index 53baa48..1596094 100644 --- a/app/Controllers/LoginController.php +++ b/app/Controllers/LoginController.php @@ -4,40 +4,44 @@ namespace App\Controllers; use App\Database\DB; -use App\Traits\SingletonController; -use App\Web\Log; use App\Web\Session; -use Flight; +use Slim\Http\Request; +use Slim\Http\Response; class LoginController extends Controller { - use SingletonController; - public function show(): void + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function show(Request $request, Response $response): Response { if (Session::get('logged', false)) { - Flight::redirect('/home'); - return; + return $response->withRedirect('/home'); } - Flight::render('auth/login.twig'); + return $this->view->render($response, 'auth/login.twig'); } - public function login(): void + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function login(Request $request, Response $response): Response { - $form = Flight::request()->data; - $result = DB::query('SELECT `id`,`username`, `password`,`is_admin`, `active` FROM `users` WHERE `username` = ? LIMIT 1', $form->username)->fetch(); + $result = DB::query('SELECT `id`,`username`, `password`,`is_admin`, `active` FROM `users` WHERE `username` = ? LIMIT 1', $request->getParam('username'))->fetch(); - if (!$result || !password_verify($form->password, $result->password)) { - Flight::redirect('login'); + if (!$result || !password_verify($request->getParam('password'), $result->password)) { Session::alert('Wrong credentials', 'danger'); - return; + return $response->withRedirect('/login'); } if (!$result->active) { - Flight::redirect('login'); Session::alert('Your account is disabled.', 'danger'); - return; + return $response->withRedirect('/login'); } Session::set('logged', true); @@ -46,23 +50,26 @@ class LoginController extends Controller Session::set('admin', $result->is_admin); Session::alert("Welcome, $result->username!", 'info'); - Log::info("User $result->username logged in."); + $this->logger->info("User $result->username logged in."); if (Session::has('redirectTo')) { - Flight::redirect(Session::get('redirectTo')); - return; + return $response->withRedirect(Session::get('redirectTo')); } - Flight::redirect('/home'); + return $response->withRedirect('/home'); } - public function logout(): void + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function logout(Request $request, Response $response): Response { - $this->checkLogin(); Session::clear(); Session::set('logged', false); Session::alert('Goodbye!', 'warning'); - Flight::redirect('/login'); + return $response->withRedirect('/login'); } } \ No newline at end of file diff --git a/app/Controllers/UploadController.php b/app/Controllers/UploadController.php index e2ebd4c..b90a526 100644 --- a/app/Controllers/UploadController.php +++ b/app/Controllers/UploadController.php @@ -3,99 +3,98 @@ namespace App\Controllers; -use App\Database\DB; -use App\Exceptions\NotFoundException; -use App\Traits\SingletonController; -use App\Web\Log; +use App\Exceptions\UnauthorizedException; use App\Web\Session; -use Flight; use League\Flysystem\FileExistsException; use League\Flysystem\FileNotFoundException; use League\Flysystem\Filesystem; +use Slim\Exception\NotFoundException; +use Slim\Http\Request; +use Slim\Http\Response; +use Slim\Http\Stream; class UploadController extends Controller { - use SingletonController; - public function upload(): void + /** + * @param Request $request + * @param Response $response + * @return Response + * @throws FileExistsException + */ + public function upload(Request $request, Response $response): Response { - $requestData = Flight::request()->data; - $response = [ - 'message' => null, - ]; + $json = ['message' => null]; - if (!isset($requestData->token)) { - $response['message'] = 'Token not specified.'; - Flight::json($response, 400); - return; + if ($request->getParam('token') === null) { + $json['message'] = 'Token not specified.'; + return $response->withJson($json, 400); } - $user = DB::query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', $requestData->token)->fetch(); + $user = $this->database->query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', $request->getParam('token'))->fetch(); if (!$user) { - $response['message'] = 'Token specified not found.'; - Flight::json($response, 404); - return; + $json['message'] = 'Token specified not found.'; + return $response->withJson($json, 404); } if (!$user->active) { - $response['message'] = 'Account disabled.'; - Flight::json($response, 401); - return; + $json['message'] = 'Account disabled.'; + return $response->withJson($json, 401); } do { $code = uniqid(); - } while (DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0); + } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0); - $file = Flight::request()->files->current(); - $fileInfo = pathinfo($file['name']); + /** @var \Psr\Http\Message\UploadedFileInterface $file */ + $file = $request->getUploadedFiles()['upload']; + + $fileInfo = pathinfo($file->getClientFilename()); $storagePath = "$user->user_code/$code.$fileInfo[extension]"; - $stream = fopen($file['tmp_name'], 'r+'); + $this->getStorage()->writeStream($storagePath, $file->getStream()->detach()); - $filesystem = $this->getStorage(); - try { - $filesystem->writeStream($storagePath, $stream); - } catch (FileExistsException $e) { - Flight::halt(500); - return; - } finally { - fclose($stream); - } - - DB::query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`) VALUES (?, ?, ?, ?)', [ + $this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`) VALUES (?, ?, ?, ?)', [ $user->id, $code, - $file['name'], + $file->getClientFilename(), $storagePath ]); - $base_url = Flight::get('config')['base_url']; + $base_url = $this->settings['base_url']; - $response['message'] = 'OK.'; - $response['url'] = "$base_url/$user->user_code/$code.$fileInfo[extension]"; - Flight::json($response, 201); + $json['message'] = 'OK.'; + $json['url'] = "$base_url/$user->user_code/$code.$fileInfo[extension]"; - Log::info("User $user->username uploaded new media.", [DB::raw()->lastInsertId()]); + $this->logger->info("User $user->username uploaded new media.", [$this->database->raw()->lastInsertId()]); + + return $response->withJson($json, 201); } - public function show($userCode, $mediaCode): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws FileNotFoundException + * @throws NotFoundException + */ + public function show(Request $request, Response $response, $args): Response { - $media = $this->getMedia($userCode, $mediaCode); + $media = $this->getMedia($args['userCode'], $args['mediaCode']); if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) { - Flight::error(new NotFoundException()); - return; + throw new NotFoundException($request, $response); } $filesystem = $this->getStorage(); - if (stristr(Flight::request()->user_agent, 'TelegramBot') || - stristr(Flight::request()->user_agent, 'facebookexternalhit/') || - stristr(Flight::request()->user_agent, 'Facebot')) { - $this->streamMedia($filesystem, $media); + if (stristr($request->getHeaderLine('User-Agent'), 'TelegramBot') || + stristr($request->getHeaderLine('User-Agent'), 'facebookexternalhit/') || + stristr($request->getHeaderLine('User-Agent'), 'Facebot')) { + return $this->streamMedia($request, $response, $filesystem, $media); } else { try { @@ -104,16 +103,15 @@ class UploadController extends Controller $type = explode('/', $mime)[0]; if ($type === 'text') { $media->text = $filesystem->read($media->storage_path); - } elseif (in_array($type, ['image', 'video'])) { - $this->http2push(Flight::get('config')['base_url'] . "/$userCode/$mediaCode/raw"); + } elseif (in_array($type, ['image', 'video']) && $request->getHeaderLine('Scheme') === 'HTTP/2.0') { + $response = $response->withHeader('Link', "<{$this->settings['base_url']}/$args[userCode]/$args[mediaCode]/raw>; rel=preload; as={$type}"); } } catch (FileNotFoundException $e) { - Flight::error($e); - return; + throw $e; } - Flight::render('upload/public.twig', [ + return $this->view->render($response, 'upload/public.twig', [ 'media' => $media, 'type' => $mime, 'extension' => pathinfo($media->filename, PATHINFO_EXTENSION) @@ -121,68 +119,98 @@ class UploadController extends Controller } } - public function getRawById($id): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws NotFoundException + * @throws FileNotFoundException + */ + public function getRawById(Request $request, Response $response, $args): Response { - $this->checkAdmin(); - $media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch(); + $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); if (!$media) { - Flight::error(new NotFoundException()); - return; + throw new NotFoundException($request, $response); } - $this->streamMedia($this->getStorage(), $media); + return $this->streamMedia($request, $response, $this->getStorage(), $media); } - public function showRaw($userCode, $mediaCode): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws NotFoundException + * @throws FileNotFoundException + */ + public function showRaw(Request $request, Response $response, $args): Response { - $media = $this->getMedia($userCode, $mediaCode); + $media = $this->getMedia($args['userCode'], $args['mediaCode']); if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) { - Flight::error(new NotFoundException()); - return; + throw new NotFoundException($request, $response); } - - $this->streamMedia($this->getStorage(), $media); + return $this->streamMedia($request, $response, $this->getStorage(), $media); } - public function download($userCode, $mediaCode): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws NotFoundException + * @throws FileNotFoundException + */ + public function download(Request $request, Response $response, $args): Response { - $media = $this->getMedia($userCode, $mediaCode); + $media = $this->getMedia($args['userCode'], $args['mediaCode']); if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) { - Flight::error(new NotFoundException()); - return; + throw new NotFoundException($request, $response); } - - $this->streamMedia($this->getStorage(), $media, 'attachment'); + return $this->streamMedia($request, $response, $this->getStorage(), $media, 'attachment'); } - public function togglePublish($id): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws NotFoundException + */ + public function togglePublish(Request $request, Response $response, $args): Response { - $this->checkLogin(); - if (Session::get('admin')) { - $media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch(); + $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); } else { - $media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? AND `user_id` = ? LIMIT 1', [$id, Session::get('user_id')])->fetch(); + $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? AND `user_id` = ? LIMIT 1', [$args['id'], Session::get('user_id')])->fetch(); } if (!$media) { - Flight::halt(404); - return; + throw new NotFoundException($request, $response); } - DB::query('UPDATE `uploads` SET `published`=? WHERE `id`=?', [!$media->published, $media->id]); + $this->database->query('UPDATE `uploads` SET `published`=? WHERE `id`=?', [!$media->published, $media->id]); + + return $response->withStatus(200); } - public function delete($id): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws NotFoundException + * @throws UnauthorizedException + */ + public function delete(Request $request, Response $response, $args): Response { - $this->checkLogin(); - - $media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch(); + $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); if (Session::get('admin', false) || $media->user_id === Session::get('user_id')) { @@ -190,22 +218,28 @@ class UploadController extends Controller try { $filesystem->delete($media->storage_path); } catch (FileNotFoundException $e) { - Flight::halt(404); - return; + throw new NotFoundException($request, $response); } finally { - DB::query('DELETE FROM `uploads` WHERE `id` = ?', $id); - Log::info('User ' . Session::get('username') . " deleted media $id"); + $this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $args['id']); + $this->logger->info('User ' . Session::get('username') . ' deleted a media.', [$args['id']]); } } else { - Flight::halt(403); + throw new UnauthorizedException(); } + + return $response->withStatus(200); } + /** + * @param $userCode + * @param $mediaCode + * @return mixed + */ protected function getMedia($userCode, $mediaCode) { $mediaCode = pathinfo($mediaCode)['filename']; - $media = DB::query('SELECT * FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1', [ + $media = $this->database->query('SELECT * FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1', [ $userCode, $mediaCode ])->fetch(); @@ -213,36 +247,43 @@ class UploadController extends Controller return $media; } - protected function streamMedia(Filesystem $storage, $media, string $disposition = 'inline'): void + /** + * @param Request $request + * @param Response $response + * @param Filesystem $storage + * @param $media + * @param string $disposition + * @return Response + * @throws FileNotFoundException + */ + protected function streamMedia(Request $request, Response $response, Filesystem $storage, $media, string $disposition = 'inline'): Response { - try { - $mime = $storage->getMimetype($media->storage_path); - $query = Flight::request()->query; + $mime = $storage->getMimetype($media->storage_path); - if ($query['width'] !== null && explode('/', $mime)[0] === 'image') { - Flight::response()->header('Content-Type', 'image/png'); - Flight::response()->header('Content-Disposition', $disposition . ';filename="scaled-' . pathinfo($media->filename)['filename'] . '.png"'); - Flight::response()->sendHeaders(); - ob_clean(); + if ($request->getParam('width') !== null && explode('/', $mime)[0] === 'image') { - $image = imagecreatefromstring($storage->read($media->storage_path)); - $scaled = imagescale($image, $query['width'], $query['height'] !== null ? $query['height'] : -1); + $image = imagecreatefromstring($storage->read($media->storage_path)); + $scaled = imagescale($image, $request->getParam('width'), $request->getParam('height') !== null ? $request->getParam('height') : -1); + imagedestroy($image); - imagedestroy($image); + ob_start(); + imagepng($scaled, null, 9); - imagepng($scaled, null, 9); - imagedestroy($scaled); - } else { - Flight::response()->header('Content-Type', $mime); - Flight::response()->header('Content-Disposition', $disposition . ';filename="' . $media->filename . '"'); - Flight::response()->header('Content-Length', $storage->getSize($media->storage_path)); - Flight::response()->sendHeaders(); - ob_end_clean(); + $imagedata = ob_get_contents(); + ob_end_clean(); + imagedestroy($scaled); - fpassthru($storage->readStream($media->storage_path)); - } - } catch (FileNotFoundException $e) { - Flight::error($e); + return $response + ->withHeader('Content-Type', 'image/png') + ->withHeader('Content-Disposition', $disposition . ';filename="scaled-' . pathinfo($media->filename)['filename'] . '.png"') + ->write($imagedata); + } else { + ob_end_clean(); + return $response + ->withHeader('Content-Type', $mime) + ->withHeader('Content-Disposition', $disposition . ';filename="' . $media->filename . '"') + ->withHeader('Content-Length', $storage->getSize($media->storage_path)) + ->withBody(new Stream($storage->readStream($media->storage_path))); } } } \ No newline at end of file diff --git a/app/Controllers/UserController.php b/app/Controllers/UserController.php index 8b3be8f..13f1c48 100644 --- a/app/Controllers/UserController.php +++ b/app/Controllers/UserController.php @@ -3,309 +3,333 @@ namespace App\Controllers; -use App\Database\DB; -use App\Exceptions\NotFoundException; use App\Exceptions\UnauthorizedException; -use App\Traits\SingletonController; -use App\Web\Log; use App\Web\Session; -use Flight; +use Slim\Exception\NotFoundException; +use Slim\Http\Request; +use Slim\Http\Response; class UserController extends Controller { - use SingletonController; - const PER_PAGE = 15; - public function index($page = 1): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + */ + public function index(Request $request, Response $response, $args): Response { - $this->checkAdmin(); - + $page = isset($args['page']) ? (int)$args['page'] : 0; $page = max(0, --$page); - $users = DB::query('SELECT * FROM `users` LIMIT ? OFFSET ?', [self::PER_PAGE, $page * self::PER_PAGE])->fetchAll(); + $users = $this->database->query('SELECT * FROM `users` LIMIT ? OFFSET ?', [self::PER_PAGE, $page * self::PER_PAGE])->fetchAll(); - $pages = DB::query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count / self::PER_PAGE; + $pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count / self::PER_PAGE; - Flight::render('user/index.twig', [ - 'users' => $users, - 'next' => $page < floor($pages), - 'previous' => $page >= 1, - 'current_page' => ++$page, - ]); + return $this->view->render($response, + 'user/index.twig', + [ + 'users' => $users, + 'next' => $page < floor($pages), + 'previous' => $page >= 1, + 'current_page' => ++$page, + ] + ); } - public function create(): void + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function create(Request $request, Response $response): Response { - $this->checkAdmin(); - Flight::render('user/create.twig'); + return $this->view->render($response, 'user/create.twig'); } - public function store(): void + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function store(Request $request, Response $response): Response { - $this->checkAdmin(); - - $form = Flight::request()->data; - - if (!isset($form->email) || empty($form->email)) { + if ($request->getParam('email') === null) { Session::alert('The email is required.', 'danger'); - Flight::redirectBack(); - return; + return $response->withRedirect('/user/create'); } - if (!isset($form->username) || empty($form->username)) { + if ($request->getParam('username') === null) { Session::alert('The username is required.', 'danger'); - Flight::redirectBack(); - return; + return $response->withRedirect('/user/create'); } - if (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', $form->username)->fetch()->count > 0) { - Session::alert('The username already taken.', 'danger'); - Flight::redirectBack(); - return; - } - - if (!isset($form->password) || empty($form->password)) { + if ($request->getParam('password') === null) { Session::alert('The password is required.', 'danger'); - Flight::redirectBack(); - return; + return $response->withRedirect('/user/create'); + } + + if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', $request->getParam('username'))->fetch()->count > 0) { + Session::alert('The username already taken.', 'danger'); + return $response->withRedirect('/user/create'); } do { $userCode = substr(md5(microtime()), rand(0, 26), 5); - } while (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0); + } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0); $token = $this->generateNewToken(); - DB::query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`) VALUES (?, ?, ?, ?, ?, ?, ?)', [ - $form->email, - $form->username, - password_hash($form->password, PASSWORD_DEFAULT), - isset($form->is_admin), - isset($form->is_active), + $this->database->query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`) VALUES (?, ?, ?, ?, ?, ?, ?)', [ + $request->getParam('email'), + $request->getParam('username'), + password_hash($request->getParam('password'), PASSWORD_DEFAULT), + $request->getParam('is_admin') !== null, + $request->getParam('is_active') !== null, $userCode, $token ]); - Session::alert("User '$form->username' created!", 'success'); - Log::info('User ' . Session::get('username') . ' created a new user.', [array_diff($form->getData(), ['password'])]); + Session::alert("User '{$request->getParam('username')}' created!", 'success'); + $this->logger->info('User ' . Session::get('username') . ' created a new user.', [array_diff($request->getParams(), ['password'])]); - Flight::redirect('/users'); + return $response->withRedirect('/users'); } - public function edit($id): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws NotFoundException + */ + public function edit(Request $request, Response $response, $args): Response { - $this->checkAdmin(); - - $user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); if (!$user) { - Flight::error(new NotFoundException()); - return; + throw new NotFoundException($request, $response); } - Flight::render('user/edit.twig', [ + return $this->view->render($response, 'user/edit.twig', [ 'user' => $user ]); } - public function update($id): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws NotFoundException + */ + public function update(Request $request, Response $response, $args): Response { - $this->checkAdmin(); - - $form = Flight::request()->data; - - $user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); if (!$user) { - Flight::error(new NotFoundException()); - return; + throw new NotFoundException($request, $response); } - if (!isset($form->email) || empty($form->email)) { + if ($request->getParam('email') === null) { Session::alert('The email is required.', 'danger'); - Flight::redirectBack(); - return; + return $response->withRedirect('/user/' . $args['id'] . '/edit'); } - if (!isset($form->username) || empty($form->username)) { + if ($request->getParam('username') === null) { Session::alert('The username is required.', 'danger'); - Flight::redirectBack(); - return; + return $response->withRedirect('/user/' . $args['id'] . '/edit'); } - if (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [$form->username, $user->username])->fetch()->count > 0) { + if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [$request->getParam('username'), $user->username])->fetch()->count > 0) { Session::alert('The username already taken.', 'danger'); - Flight::redirectBack(); - return; + return $response->withRedirect('/user/' . $args['id'] . '/edit'); } - if ($user->id === Session::get('user_id') && !isset($form->is_admin)) { + if ($user->id === Session::get('user_id') && $request->getParam('is_admin') === null) { Session::alert('You cannot demote yourself.', 'danger'); - Flight::redirectBack(); - return; + return $response->withRedirect('/user/' . $args['id'] . '/edit'); } - if (isset($form->password) && !empty($form->password)) { - DB::query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [ - $form->email, - $form->username, - password_hash($form->password, PASSWORD_DEFAULT), - isset($form->is_admin), - isset($form->is_active), + if ($request->getParam('password') !== null && !empty($request->getParam('password'))) { + $this->database->query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [ + $request->getParam('email'), + $request->getParam('username'), + password_hash($request->getParam('password'), PASSWORD_DEFAULT), + $request->getParam('is_admin') !== null, + $request->getParam('is_active') !== null, $user->id ]); } else { - DB::query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [ - $form->email, - $form->username, - isset($form->is_admin), - isset($form->is_active), + $this->database->query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [ + $request->getParam('email'), + $request->getParam('username'), + $request->getParam('is_admin') !== null, + $request->getParam('is_active') !== null, $user->id ]); } - Session::alert("User '$form->username' updated!", 'success'); - Log::info('User ' . Session::get('username') . " updated $user->id.", [$user, array_diff($form->getData(), ['password'])]); + Session::alert("User '{$request->getParam('username')}' updated!", 'success'); + $this->logger->info('User ' . Session::get('username') . " updated $user->id.", [$user, array_diff($request->getParams(), ['password'])]); - Flight::redirect('/users'); + return $response->withRedirect('/users'); } - public function delete($id): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws NotFoundException + */ + public function delete(Request $request, Response $response, $args): Response { - $this->checkAdmin(); - - $user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); if (!$user) { - Flight::error(new NotFoundException()); - return; + throw new NotFoundException($request, $response); } if ($user->id === Session::get('user_id')) { Session::alert('You cannot delete yourself.', 'danger'); - Flight::redirectBack(); - return; + return $response->withRedirect('/users'); } - DB::query('DELETE FROM `users` WHERE `id` = ?', $user->id); + $this->database->query('DELETE FROM `users` WHERE `id` = ?', $user->id); Session::alert('User deleted.', 'success'); - Log::info('User ' . Session::get('username') . " deleted $user->id."); + $this->logger->info('User ' . Session::get('username') . " deleted $user->id."); - Flight::redirect('/users'); + return $response->withRedirect('/users'); } - public function profile(): void + /** + * @param Request $request + * @param Response $response + * @return Response + * @throws NotFoundException + * @throws UnauthorizedException + */ + public function profile(Request $request, Response $response): Response { - $this->checkLogin(); - - $user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', Session::get('user_id'))->fetch(); + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', Session::get('user_id'))->fetch(); if (!$user) { - Flight::error(new NotFoundException()); - return; + throw new NotFoundException($request, $response); } if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) { - Flight::error(new UnauthorizedException()); - return; + throw new UnauthorizedException(); } - Flight::render('user/profile.twig', [ + return $this->view->render($response, 'user/profile.twig', [ 'user' => $user ]); } - public function profileEdit($id): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws NotFoundException + * @throws UnauthorizedException + */ + public function profileEdit(Request $request, Response $response, $args): Response { - $this->checkLogin(); - - $form = Flight::request()->data; - - $user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); if (!$user) { - Flight::error(new NotFoundException()); - return; + throw new NotFoundException($request, $response); } if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) { - Flight::error(new UnauthorizedException()); - return; + throw new UnauthorizedException(); } - if (!isset($form->email) || empty($form->email)) { + if ($request->getParam('email') === null) { Session::alert('The email is required.', 'danger'); - Flight::redirectBack(); - return; + return $response->withRedirect('/profile'); } - if (isset($form->password) && !empty($form->password)) { - DB::query('UPDATE `users` SET `email`=?, `password`=? WHERE `id` = ?', [ - $form->email, - password_hash($form->password, PASSWORD_DEFAULT), + if ($request->getParam('password') !== null && !empty($request->getParam('password'))) { + $this->database->query('UPDATE `users` SET `email`=?, `password`=? WHERE `id` = ?', [ + $request->getParam('email'), + password_hash($request->getParam('password'), PASSWORD_DEFAULT), $user->id ]); } else { - DB::query('UPDATE `users` SET `email`=? WHERE `id` = ?', [ - $form->email, + $this->database->query('UPDATE `users` SET `email`=? WHERE `id` = ?', [ + $request->getParam('email'), $user->id ]); } Session::alert('Profile updated successfully!', 'success'); - Log::info('User ' . Session::get('username') . " updated profile of $user->id."); + $this->logger->info('User ' . Session::get('username') . " updated profile of $user->id."); - Flight::redirectBack(); + return $response->withRedirect('/profile'); } - public function refreshToken($id): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws NotFoundException + * @throws UnauthorizedException + */ + public function refreshToken(Request $request, Response $response, $args): Response { - $this->checkLogin(); - - $user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); if (!$user) { - Flight::halt(404); - return; + throw new NotFoundException($request, $response); } if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) { - Flight::halt(403); - return; + throw new UnauthorizedException(); } $token = $this->generateNewToken(); - DB::query('UPDATE `users` SET `token`=? WHERE `id` = ?', [ + $this->database->query('UPDATE `users` SET `token`=? WHERE `id` = ?', [ $token, $user->id ]); - Log::info('User ' . Session::get('username') . " refreshed token of user $user->id."); + $this->logger->info('User ' . Session::get('username') . " refreshed token of user $user->id."); - echo $token; + $response->getBody()->write($token); + + return $response; } - public function getShareXconfigFile($id): void + /** + * @param Request $request + * @param Response $response + * @param $args + * @return Response + * @throws NotFoundException + * @throws UnauthorizedException + */ + public function getShareXconfigFile(Request $request, Response $response, $args): Response { - $this->checkLogin(); - - $user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); if (!$user) { - Flight::halt(404); - return; + throw new NotFoundException($request, $response); } if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) { - Flight::halt(403); - return; + throw new UnauthorizedException(); } - $base_url = Flight::get('config')['base_url']; + $base_url = $this->settings['base_url']; $json = [ 'DestinationType' => 'ImageUploader, TextUploader, FileUploader', 'RequestURL' => "$base_url/upload", @@ -319,18 +343,19 @@ class UserController extends Controller 'ThumbnailURL' => '$json:url$/raw', ]; - Flight::response()->header('Content-Type', 'application/json'); - Flight::response()->header('Content-Disposition', 'attachment;filename="' . $user->username . '-ShareX.sxcu"'); - Flight::response()->sendHeaders(); - - echo json_encode($json, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); + return $response + ->withHeader('Content-Disposition', 'attachment;filename="' . $user->username . '-ShareX.sxcu"') + ->withJson($json, 200, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); } + /** + * @return string + */ protected function generateNewToken(): string { do { $token = 'token_' . md5(uniqid('', true)); - } while (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0); + } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0); return $token; } diff --git a/app/Database/DB.php b/app/Database/DB.php index 9d77303..e199b3b 100644 --- a/app/Database/DB.php +++ b/app/Database/DB.php @@ -21,7 +21,7 @@ class DB protected $pdo; /** @var string */ - protected static $dsn = 'database.db'; + protected static $dsn = 'sqlite:database.db'; /** @var string */ protected $currentDriver; @@ -67,6 +67,15 @@ class DB return $this->currentDriver; } + public static function getInstance(): DB + { + if (self::$instance === null) { + self::$instance = new self(self::$dsn, self::$username, self::$password); + } + + return self::$instance; + } + /** * Perform a query * @param string $query @@ -76,11 +85,7 @@ class DB public static function query(string $query, $parameters = []) { - if (self::$instance === null) { - self::$instance = new self(self::$dsn, self::$username, self::$password); - } - - return self::$instance->doQuery($query, $parameters); + return self::getInstance()->doQuery($query, $parameters); } /** @@ -90,11 +95,7 @@ class DB public static function driver(): string { - if (self::$instance === null) { - self::$instance = new self(self::$dsn, self::$username, self::$password); - } - - return self::$instance->getCurrentDriver(); + return self::getInstance()->getCurrentDriver(); } /** @@ -103,11 +104,8 @@ class DB */ public static function raw(): PDO { - if (self::$instance === null) { - self::$instance = new self(self::$dsn, self::$username, self::$password); - } - return self::$instance->getPdo(); + return self::getInstance()->getPdo(); } /** diff --git a/app/Exceptions/AuthenticationException.php b/app/Exceptions/AuthenticationException.php deleted file mode 100644 index c1d7f2d..0000000 --- a/app/Exceptions/AuthenticationException.php +++ /dev/null @@ -1,15 +0,0 @@ -container = $container; + } + + /** + * @param Request $request + * @param Response $response + * @param callable $next + * @return Response + * @throws UnauthorizedException + */ + public function __invoke(Request $request, Response $response, callable $next) + { + if (!$this->container->database->query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->is_admin) { + Session::alert('Your account is not admin anymore.', 'danger'); + Session::set('admin', false); + Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); + throw new UnauthorizedException(); + } + + return $next($request, $response); + } + +} \ No newline at end of file diff --git a/app/Middleware/AuthMiddleware.php b/app/Middleware/AuthMiddleware.php new file mode 100644 index 0000000..5610958 --- /dev/null +++ b/app/Middleware/AuthMiddleware.php @@ -0,0 +1,44 @@ +container = $container; + } + + /** + * @param Request $request + * @param Response $response + * @param callable $next + * @return Response + */ + public function __invoke(Request $request, Response $response, callable $next) + { + if (!Session::get('logged', false)) { + Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); + return $response->withRedirect('/login'); + } + + if (!$this->container->database->query('SELECT `id`, `active` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->active) { + Session::alert('Your account is not active anymore.', 'danger'); + Session::set('logged', false); + Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); + return $response->withRedirect('/login'); + } + + return $next($request, $response); + } + +} \ No newline at end of file diff --git a/app/Traits/SingletonController.php b/app/Traits/SingletonController.php deleted file mode 100644 index 210e5b6..0000000 --- a/app/Traits/SingletonController.php +++ /dev/null @@ -1,24 +0,0 @@ -logger = new Logger(self::class); - - $streamHandler = new RotatingFileHandler(__DIR__ . '/../../logs/log.txt', 10, Logger::DEBUG); - $streamHandler->setFormatter(new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", "Y-m-d H:i:s", true)); - - $this->logger->pushHandler($streamHandler); - } - - public static function debug(string $message, array $context = []) - { - self::instance()->logger->debug($message, $context); - } - - public static function info(string $message, array $context = []) - { - self::instance()->logger->info($message, $context); - } - - public static function warning(string $message, array $context = []) - { - self::instance()->logger->warning($message, $context); - } - - public static function error(string $message, array $context = []) - { - self::instance()->logger->error($message, $context); - } - - public static function critical(string $message, array $context = []) - { - self::instance()->logger->critical($message, $context); - } -} \ No newline at end of file diff --git a/app/routes.php b/app/routes.php index 73e4e60..3b46829 100644 --- a/app/routes.php +++ b/app/routes.php @@ -1,29 +1,37 @@ group('', function () { + $this->get('/', \App\Controllers\DashboardController::class . ':redirects'); + $this->get('/home[/page/{page}]', \App\Controllers\DashboardController::class . ':home'); + $this->get('/system', \App\Controllers\DashboardController::class . ':system')->add(\App\Middleware\AdminMiddleware::class); -Flight::route('GET /login', [\App\Controllers\LoginController::instance(), 'show']); -Flight::route('POST /login', [\App\Controllers\LoginController::instance(), 'login']); -Flight::route('GET|POST /logout', [\App\Controllers\LoginController::instance(), 'logout']); + $this->group('', function () { + $this->get('/users[/page/{page}]', \App\Controllers\UserController::class . ':index'); + $this->get('/user/create', \App\Controllers\UserController::class . ':create'); + $this->post('/user/create', \App\Controllers\UserController::class . ':store'); + $this->get('/user/{id}/edit', \App\Controllers\UserController::class . ':edit'); + $this->post('/user/{id}', \App\Controllers\UserController::class . ':update'); + $this->get('/user/{id}/delete', \App\Controllers\UserController::class . ':delete'); + })->add(\App\Middleware\AdminMiddleware::class); -Flight::route('GET /', [\App\Controllers\DashboardController::instance(), 'redirects']); -Flight::route('GET /home(/page/@page)', [\App\Controllers\DashboardController::instance(), 'home']); -Flight::route('GET /system', [\App\Controllers\DashboardController::instance(), 'system']); + $this->get('/profile', \App\Controllers\UserController::class . ':profile'); + $this->post('/profile/{id}/edit', \App\Controllers\UserController::class . ':profileEdit'); + $this->post('/user/{id}/refreshToken', \App\Controllers\UserController::class . ':refreshToken'); + $this->get('/user/{id}/config/sharex', \App\Controllers\UserController::class . ':getShareXconfigFile'); -Flight::route('GET /users(/page/@page)', [\App\Controllers\UserController::instance(), 'index']); -Flight::route('GET /user/add', [\App\Controllers\UserController::instance(), 'create']); -Flight::route('POST /user/add', [\App\Controllers\UserController::instance(), 'store']); -Flight::route('GET /user/@id/edit', [\App\Controllers\UserController::instance(), 'edit']); -Flight::route('POST /user/@id', [\App\Controllers\UserController::instance(), 'update']); -Flight::route('GET /user/@id/delete', [\App\Controllers\UserController::instance(), 'delete']); -Flight::route('GET /profile', [\App\Controllers\UserController::instance(), 'profile']); -Flight::route('POST /profile/@id/edit', [\App\Controllers\UserController::instance(), 'profileEdit']); -Flight::route('POST /user/@id/refreshToken', [\App\Controllers\UserController::instance(), 'refreshToken']); -Flight::route('GET /user/@id/config/sharex', [\App\Controllers\UserController::instance(), 'getShareXconfigFile']); + $this->post('/upload/{id}/publish', \App\Controllers\UploadController::class . ':togglePublish'); + $this->post('/upload/{id}/unpublish', \App\Controllers\UploadController::class . ':togglePublish'); + $this->get('/upload/{id}/raw', \App\Controllers\UploadController::class . ':getRawById')->add(\App\Middleware\AdminMiddleware::class); + $this->post('/upload/{id}/delete', \App\Controllers\UploadController::class . ':delete'); -Flight::route('POST /upload', [\App\Controllers\UploadController::instance(), 'upload']); -Flight::route('POST /upload/@id/publish', [\App\Controllers\UploadController::instance(), 'togglePublish']); -Flight::route('POST /upload/@id/unpublish', [\App\Controllers\UploadController::instance(), 'togglePublish']); -Flight::route('GET /upload/@id/raw', [\App\Controllers\UploadController::instance(), 'getRawById']); -Flight::route('POST /upload/@id/delete', [\App\Controllers\UploadController::instance(), 'delete']); -Flight::route('GET /@userCode/@filename', [\App\Controllers\UploadController::instance(), 'show']); -Flight::route('GET /@userCode/@filename/raw', [\App\Controllers\UploadController::instance(), 'showRaw']); -Flight::route('GET /@userCode/@filename/download', [\App\Controllers\UploadController::instance(), 'download']); \ No newline at end of file +})->add(\App\Middleware\AuthMiddleware::class); + +$app->get('/login', \App\Controllers\LoginController::class . ':show'); +$app->post('/login', \App\Controllers\LoginController::class . ':login'); +$app->map(['GET', 'POST'], '/logout', \App\Controllers\LoginController::class . ':logout'); + +$app->post('/upload', \App\Controllers\UploadController::class . ':upload'); + +$app->get('/{userCode}/{mediaCode}', \App\Controllers\UploadController::class . ':show'); +$app->get('/{userCode}/{mediaCode}/raw', \App\Controllers\UploadController::class . ':showRaw'); +$app->get('/{userCode}/{mediaCode}/download', \App\Controllers\UploadController::class . ':download'); \ No newline at end of file diff --git a/bootstrap/app.php b/bootstrap/app.php index fd8933e..28ba294 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,13 +1,19 @@ 'XBackBone', 'base_url' => isset($_SERVER['HTTPS']) ? 'https://' . $_SERVER['HTTP_HOST'] : 'http://' . $_SERVER['HTTP_HOST'], 'storage_dir' => 'storage', - 'debug' => false, + 'displayErrorDetails' => false, 'db' => [ 'connection' => 'sqlite', 'dsn' => 'resources/database/xbackbone.db', @@ -16,68 +22,68 @@ $config = array_replace_recursive([ ], ], require 'config.php'); -// Set flight parameters -Flight::set('flight.base_url', $config['base_url']); -Flight::set('flight.log_errors', false); -Flight::set('config', $config); +$container = new Container(['settings' => $config]); + +$container['logger'] = function ($container) { + $logger = new Logger('app'); + + $streamHandler = new RotatingFileHandler(__DIR__ . '/../logs/log.txt', 10, Logger::DEBUG); + $streamHandler->setFormatter(new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", "Y-m-d H:i:s", true)); + + $logger->pushHandler($streamHandler); + + return $logger; +}; + // Set the database dsn DB::setDsn($config['db']['connection'] . ':' . $config['db']['dsn'], $config['db']['username'], $config['db']['password']); -// Register the Twig instance -Flight::register('view', 'Twig_Environment', - [new Twig_Loader_Filesystem('resources/templates'), - [ - 'cache' => 'resources/cache', - 'autoescape' => 'html', - 'debug' => $config['debug'], - 'auto_reload' => $config['debug'], - ] - ] -); +$container['database'] = function ($container) use (&$config) { + return DB::getInstance(); +}; -// Redirect back helper -Flight::register('redirectBack', function () { - Flight::redirect(Flight::request()->referrer); -}); -// Map the render call to the Twig view instance -Flight::map('render', function (string $template, array $data = []) use (&$config) { - Flight::view()->addGlobal('config', $config); - Flight::view()->addGlobal('request', Flight::request()); - Flight::view()->addGlobal('alerts', App\Web\Session::getAlert()); - Flight::view()->addGlobal('session', App\Web\Session::all()); - Flight::view()->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION); - Flight::view()->display($template, $data); -}); +$container['view'] = function ($container) use (&$config) { + $view = new \Slim\Views\Twig('resources/templates', [ + 'cache' => 'resources/cache', + 'autoescape' => 'html', + 'debug' => $config['displayErrorDetails'], + 'auto_reload' => $config['displayErrorDetails'], + ]); -// The application error handler -Flight::map('error', function (Exception $exception) { - if ($exception instanceof \App\Exceptions\AuthenticationException) { - Flight::redirect('/login'); - return; - } + // Instantiate and add Slim specific extension + $router = $container->get('router'); + $uri = \Slim\Http\Uri::createFromEnvironment(new \Slim\Http\Environment($_SERVER)); + $view->addExtension(new Slim\Views\TwigExtension($router, $uri)); - if ($exception instanceof \App\Exceptions\UnauthorizedException) { - Flight::response()->status(403); - Flight::render('errors/403.twig'); - return; - } + $view->getEnvironment()->addGlobal('config', $config); + $view->getEnvironment()->addGlobal('request', $container->get('request')); + $view->getEnvironment()->addGlobal('alerts', Session::getAlert()); + $view->getEnvironment()->addGlobal('session', Session::all()); + $view->getEnvironment()->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION); + return $view; +}; - if ($exception instanceof \App\Exceptions\NotFoundException) { - Flight::response()->status(404); - Flight::render('errors/404.twig'); - return; - } +$container['errorHandler'] = function ($container) { + return function (\Slim\Http\Request $request, \Slim\Http\Response $response, $exception) use (&$container) { - Flight::response()->status(500); - \App\Web\Log::critical('Fatal error during app execution', [$exception->getTraceAsString()]); - Flight::render('errors/500.twig', ['exception' => $exception]); -}); + if ($exception instanceof \App\Exceptions\UnauthorizedException) { + return $container->view->render($response->withStatus(403), 'errors/403.twig'); + } -Flight::map('notFound', function () { - Flight::render('errors/404.twig'); -}); + $container->logger->critical('Fatal error during app execution', [$exception, $exception->getTraceAsString()]); + return $container->view->render($response->withStatus(500), 'errors/500.twig', ['exception' => $exception]); + }; +}; +$container['notFoundHandler'] = function ($container) { + return function (\Slim\Http\Request $request, \Slim\Http\Response $response) use (&$container) { + $response->withStatus(404)->withHeader('Content-Type', 'text/html'); + return $container->view->render($response, 'errors/404.twig'); + }; +}; + +$app = new App($container); // Load the application routes require 'app/routes.php'; \ No newline at end of file diff --git a/composer.json b/composer.json index 6cbd0a0..0f1c379 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,14 @@ { "name": "sergix44/xbackbone", - "version": "1.3", + "version": "2.0", "description": "A lightweight ShareX PHP backend", "type": "project", "require": { - "mikecao/flight": "^1.3", - "league/flysystem": "^1.0.45", - "twig/twig": "~2.0", - "monolog/monolog": "^1.23", "php": ">=7.1", + "slim/slim": "^3.0", + "slim/twig-view": "^2.4", + "league/flysystem": "^1.0.45", + "monolog/monolog": "^1.23", "ext-json": "*", "ext-gd": "*", "ext-pdo": "*" diff --git a/composer.lock b/composer.lock index b7af51f..4290b43 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,51 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "b2d12fd3c7627ea2da10cbb0bc3cc35d", + "content-hash": "c1434c69fd6bdbfc70c7ca0b185ffdc9", "packages": [ { - "name": "league/flysystem", - "version": "1.0.47", + "name": "container-interop/container-interop", + "version": "1.2.0", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "a11e4a75f256bdacf99d20780ce42d3b8272975c" + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a11e4a75f256bdacf99d20780ce42d3b8272975c", - "reference": "a11e4a75f256bdacf99d20780ce42d3b8272975c", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "time": "2017-02-14T19:40:03+00:00" + }, + { + "name": "league/flysystem", + "version": "1.0.48", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "a6ded5b2f6055e2db97b4b859fdfca2b952b78aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a6ded5b2f6055e2db97b4b859fdfca2b952b78aa", + "reference": "a6ded5b2f6055e2db97b4b859fdfca2b952b78aa", "shasum": "" }, "require": { @@ -88,63 +119,20 @@ "sftp", "storage" ], - "time": "2018-09-14T15:30:29+00:00" - }, - { - "name": "mikecao/flight", - "version": "v1.3.5", - "source": { - "type": "git", - "url": "https://github.com/mikecao/flight.git", - "reference": "e146b8c0ddbc6384c5a66f23dea71743fc282289" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mikecao/flight/zipball/e146b8c0ddbc6384c5a66f23dea71743fc282289", - "reference": "e146b8c0ddbc6384c5a66f23dea71743fc282289", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.6" - }, - "type": "library", - "autoload": { - "files": [ - "flight/autoload.php", - "flight/Flight.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike Cao", - "email": "mike@mikecao.com", - "homepage": "http://www.mikecao.com/", - "role": "Original Developer" - } - ], - "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications.", - "homepage": "http://flightphp.com", - "time": "2017-10-20T01:46:56+00:00" + "time": "2018-10-15T13:53:10+00:00" }, { "name": "monolog/monolog", - "version": "1.23.0", + "version": "1.24.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", "shasum": "" }, "require": { @@ -209,7 +197,202 @@ "logging", "psr-3" ], - "time": "2017-06-19T01:22:40+00:00" + "time": "2018-11-05T09:00:11+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "pimple/pimple", + "version": "v3.2.3", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/container": "^1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2018-01-21T07:42:36+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" }, { "name": "psr/log", @@ -258,9 +441,131 @@ ], "time": "2016-10-10T12:19:37+00:00" }, + { + "name": "slim/slim", + "version": "3.11.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/d378e70431e78ee92ee32ddde61ecc72edf5dc0a", + "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "nikic/fast-route": "^1.0", + "php": ">=5.5.0", + "pimple/pimple": "^3.0", + "psr/container": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "https://slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ], + "time": "2018-09-16T10:54:21+00:00" + }, + { + "name": "slim/twig-view", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Twig-View.git", + "reference": "78386c01a97f7870462b38fff759dad649da9efc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Twig-View/zipball/78386c01a97f7870462b38fff759dad649da9efc", + "reference": "78386c01a97f7870462b38fff759dad649da9efc", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "psr/http-message": "^1.0", + "twig/twig": "^1.18|^2.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.7", + "slim/slim": "^3.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Views\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework 3 view helper built on top of the Twig 2 templating component", + "homepage": "http://slimframework.com", + "keywords": [ + "framework", + "slim", + "template", + "twig", + "view" + ], + "time": "2018-05-07T10:54:29+00:00" + }, { "name": "symfony/polyfill-ctype", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -318,16 +623,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", "shasum": "" }, "require": { @@ -373,7 +678,7 @@ "portable", "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2018-09-21T13:07:52+00:00" }, { "name": "twig/twig", diff --git a/index.php b/index.php index 23f8a68..5dfb172 100644 --- a/index.php +++ b/index.php @@ -5,4 +5,4 @@ define('PLATFORM_VERSION', json_decode(file_get_contents('composer.json'))->vers require 'bootstrap/app.php'; -Flight::start(); +$app->run(); diff --git a/resources/templates/errors/500.twig b/resources/templates/errors/500.twig index f659596..9cd6308 100644 --- a/resources/templates/errors/500.twig +++ b/resources/templates/errors/500.twig @@ -12,7 +12,7 @@ - {% if config.debug %} + {% if config.displayErrorDetails %}