diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php index de84918..f3e2e17 100644 --- a/app/Controllers/AdminController.php +++ b/app/Controllers/AdminController.php @@ -4,90 +4,92 @@ namespace App\Controllers; use League\Flysystem\FileNotFoundException; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; class AdminController extends Controller { - /** - * @param Request $request - * @param Response $response - * @return Response - * @throws FileNotFoundException - */ - public function system(Request $request, Response $response): Response - { - $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; + /** + * @param Response $response + * @return Response + * @throws FileNotFoundException + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function system(Response $response): Response + { + $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; - $medias = $this->database->query('SELECT `uploads`.`storage_path` FROM `uploads`')->fetchAll(); + $medias = $this->database->query('SELECT `uploads`.`storage_path` FROM `uploads`')->fetchAll(); - $totalSize = 0; + $totalSize = 0; - $filesystem = $this->storage; - foreach ($medias as $media) { - $totalSize += $filesystem->getSize($media->storage_path); - } + $filesystem = $this->storage; + foreach ($medias as $media) { + $totalSize += $filesystem->getSize($media->storage_path); + } - return $this->view->render($response, 'dashboard/system.twig', [ - 'usersCount' => $usersCount, - 'mediasCount' => $mediasCount, - 'orphanFilesCount' => $orphanFilesCount, - 'totalSize' => humanFileSize($totalSize), - 'post_max_size' => ini_get('post_max_size'), - 'upload_max_filesize' => ini_get('upload_max_filesize'), - 'installed_lang' => $this->lang->getList(), - ]); - } + return view()->render($response, 'dashboard/system.twig', [ + 'usersCount' => $usersCount, + 'mediasCount' => $mediasCount, + 'orphanFilesCount' => $orphanFilesCount, + 'totalSize' => humanFileSize($totalSize), + 'post_max_size' => ini_get('post_max_size'), + 'upload_max_filesize' => ini_get('upload_max_filesize'), + 'installed_lang' => $this->lang->getList(), + ]); + } - /** - * @param Request $request - * @param Response $response - * @return Response - */ - public function deleteOrphanFiles(Request $request, Response $response): Response - { - $orphans = $this->database->query('SELECT * FROM `uploads` WHERE `user_id` IS NULL')->fetchAll(); + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function deleteOrphanFiles(Response $response): Response + { + $orphans = $this->database->query('SELECT * FROM `uploads` WHERE `user_id` IS NULL')->fetchAll(); - $filesystem = $this->storage; - $deleted = 0; + $filesystem = $this->storage; + $deleted = 0; - foreach ($orphans as $orphan) { - try { - $filesystem->delete($orphan->storage_path); - $deleted++; - } catch (FileNotFoundException $e) { - } - } + foreach ($orphans as $orphan) { + try { + $filesystem->delete($orphan->storage_path); + $deleted++; + } catch (FileNotFoundException $e) { + } + } - $this->database->query('DELETE FROM `uploads` WHERE `user_id` IS NULL'); + $this->database->query('DELETE FROM `uploads` WHERE `user_id` IS NULL'); - $this->session->alert(lang('deleted_orphans', [$deleted])); + $this->session->alert(lang('deleted_orphans', [$deleted])); - return redirect($response, 'system'); - } + return redirect($response, route('system')); + } - /** - * @param Request $request - * @param Response $response - * @return Response - */ - public function applyLang(Request $request, Response $response): Response - { - $config = require BASE_DIR . 'config.php'; + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function applyLang(Request $request, Response $response): Response + { + $config = require BASE_DIR.'config.php'; - if ($request->getParam('lang') !== 'auto') { - $config['lang'] = $request->getParam('lang'); - } else { - unset($config['lang']); - } + if (param($request,'lang') !== 'auto') { + $config['lang'] = param($request,'lang'); + } else { + unset($config['lang']); + } - file_put_contents(BASE_DIR . 'config.php', 'session->alert(lang('lang_set', [$request->getParam('lang')])); + $this->session->alert(lang('lang_set', [param($request, 'lang')])); - return redirect($response, 'system'); - } + return redirect($response, route('system')); + } } \ No newline at end of file diff --git a/app/Controllers/Controller.php b/app/Controllers/Controller.php index 25da84e..d8882b3 100644 --- a/app/Controllers/Controller.php +++ b/app/Controllers/Controller.php @@ -5,63 +5,67 @@ namespace App\Controllers; use App\Database\DB; use App\Web\Lang; use App\Web\Session; +use DI\Container; +use DI\DependencyException; +use DI\NotFoundException; use League\Flysystem\FileNotFoundException; use League\Flysystem\Filesystem; use Monolog\Logger; -use Slim\Container; +use Twig\Environment; /** * @property Session|null session - * @property mixed|null view + * @property Environment view * @property DB|null database * @property Logger|null logger * @property Filesystem|null storage * @property Lang lang - * @property array settings + * @property array config */ abstract class Controller { - /** @var Container */ - protected $container; + /** @var Container */ + protected $container; - public function __construct(Container $container) - { - $this->container = $container; - } + public function __construct(Container $container) + { + $this->container = $container; + } - /** - * @param $name - * @return mixed|null - * @throws \Interop\Container\Exception\ContainerException - */ - public function __get($name) - { - if ($this->container->has($name)) { - return $this->container->get($name); - } - return null; - } + /** + * @param $name + * @return mixed|null + * @throws DependencyException + * @throws NotFoundException + */ + public function __get($name) + { + if ($this->container->has($name)) { + return $this->container->get($name); + } + return null; + } - /** - * @param $id - * @return int - */ - protected function getUsedSpaceByUser($id): int - { - $medias = $this->database->query('SELECT `uploads`.`storage_path` FROM `uploads` WHERE `user_id` = ?', $id); + /** + * @param $id + * @return int + */ + protected function getUsedSpaceByUser($id): int + { + $medias = $this->database->query('SELECT `uploads`.`storage_path` FROM `uploads` WHERE `user_id` = ?', $id); - $totalSize = 0; + $totalSize = 0; - $filesystem = $this->storage; - foreach ($medias as $media) { - try { - $totalSize += $filesystem->getSize($media->storage_path); - } catch (FileNotFoundException $e) { - $this->logger->error('Error calculating file size', ['exception' => $e]); - } - } + $filesystem = $this->storage; + foreach ($medias as $media) { + try { + $totalSize += $filesystem->getSize($media->storage_path); + } catch (FileNotFoundException $e) { + $this->logger->error('Error calculating file size', ['exception' => $e]); + } + } - return $totalSize; - } + return $totalSize; + } } \ No newline at end of file diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index 266dbe1..0976a5f 100644 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -3,78 +3,79 @@ namespace App\Controllers; use App\Database\Queries\MediaQuery; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; class DashboardController extends Controller { - /** - * @param Request $request - * @param Response $response - * @return Response - */ - public function redirects(Request $request, Response $response): Response - { - if ($request->getParam('afterInstall') !== null && !is_dir(BASE_DIR . 'install')) { - $this->session->alert(lang('installed'), 'success'); - } + /** + * @Inject + * @param Request $request + * @param Response $response + * @return Response + */ + public function redirects(Request $request, Response $response): Response + { + if (param($request, 'afterInstall') !== null && !is_dir(BASE_DIR.'install')) { + $this->session->alert(lang('installed'), 'success'); + } - return redirect($response, 'home'); - } + return redirect($response, route('home')); + } - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - */ - public function home(Request $request, Response $response, $args): Response - { - $page = isset($args['page']) ? (int)$args['page'] : 0; - $page = max(0, --$page); + /** + * @param Request $request + * @param Response $response + * @param int|null $page + * @return Response + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function home(Request $request, Response $response, int $page = 0): Response + { + $page = max(0, --$page); - $query = new MediaQuery($this->database, $this->session->get('admin', false), $this->storage); + $query = new MediaQuery($this->database, $this->session->get('admin', false), $this->storage); - switch ($request->getParam('sort', 'time')) { - case 'size': - $order = MediaQuery::ORDER_SIZE; - break; - case 'name': - $order = MediaQuery::ORDER_NAME; - break; - default: - case 'time': - $order = MediaQuery::ORDER_TIME; - break; - } + switch (param($request, 'sort', 'time')) { + case 'size': + $order = MediaQuery::ORDER_SIZE; + break; + case 'name': + $order = MediaQuery::ORDER_NAME; + break; + default: + case 'time': + $order = MediaQuery::ORDER_TIME; + break; + } - $query->orderBy($order, $request->getParam('order', 'DESC')) - ->withUserId($this->session->get('user_id')) - ->search($request->getParam('search', null)) - ->run($page); + $query->orderBy($order, param($request, 'order', 'DESC')) + ->withUserId($this->session->get('user_id')) + ->search(param($request, 'search', null)) + ->run($page); - return $this->view->render( - $response, - ($this->session->get('admin', false) && $this->session->get('gallery_view', true)) ? 'dashboard/admin.twig' : 'dashboard/home.twig', - [ - 'medias' => $query->getMedia(), - 'next' => $page < floor($query->getPages()), - 'previous' => $page >= 1, - 'current_page' => ++$page, - ] - ); - } + return view()->render( + $response, + ($this->session->get('admin', false) && $this->session->get('gallery_view', true)) ? 'dashboard/admin.twig' : 'dashboard/home.twig', + [ + 'medias' => $query->getMedia(), + 'next' => $page < floor($query->getPages()), + 'previous' => $page >= 1, + 'current_page' => ++$page, + ] + ); + } - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - */ - public function switchView(Request $request, Response $response, $args): Response - { - $this->session->set('gallery_view', !$this->session->get('gallery_view', true)); - return redirect($response, 'home'); - } + /** + * @param Response $response + * @return Response + */ + public function switchView(Response $response): Response + { + $this->session->set('gallery_view', !$this->session->get('gallery_view', true)); + return redirect($response, route('home')); + } } \ No newline at end of file diff --git a/app/Controllers/LoginController.php b/app/Controllers/LoginController.php index 3a7bcf1..3352455 100644 --- a/app/Controllers/LoginController.php +++ b/app/Controllers/LoginController.php @@ -3,77 +3,78 @@ namespace App\Controllers; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; class LoginController extends Controller { - /** - * @param Request $request - * @param Response $response - * @return Response - */ - public function show(Request $request, Response $response): Response - { - if ($this->session->get('logged', false)) { - return redirect($response, 'home'); - } - return $this->view->render($response, 'auth/login.twig'); - } + /** + * @param Response $response + * @return Response + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function show(Response $response): Response + { + if ($this->session->get('logged', false)) { + return redirect($response, route('home')); + } + return view()->render($response, 'auth/login.twig'); + } - /** - * @param Request $request - * @param Response $response - * @return Response - */ - public function login(Request $request, Response $response): Response - { + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function login(Request $request, Response $response): Response + { + $username = param($request, 'username'); + $result = $this->database->query('SELECT `id`, `email`, `username`, `password`,`is_admin`, `active` FROM `users` WHERE `username` = ? OR `email` = ? LIMIT 1', [$username, $username])->fetch(); - $result = $this->database->query('SELECT `id`, `email`, `username`, `password`,`is_admin`, `active` FROM `users` WHERE `username` = ? OR `email` = ? LIMIT 1', [$request->getParam('username'), $request->getParam('username')])->fetch(); + if (!$result || !password_verify(param($request, 'password'), $result->password)) { + $this->session->alert(lang('bad_login'), 'danger'); + return redirect($response, route('login')); + } - if (!$result || !password_verify($request->getParam('password'), $result->password)) { - $this->session->alert(lang('bad_login'), 'danger'); - return redirect($response, 'login'); - } + if (isset($this->config['maintenance']) && $this->config['maintenance'] && !$result->is_admin) { + $this->session->alert(lang('maintenance_in_progress'), 'info'); + return redirect($response, route('login')); + } - if (isset($this->settings['maintenance']) && $this->settings['maintenance'] && !$result->is_admin) { - $this->session->alert(lang('maintenance_in_progress'), 'info'); - return redirect($response, 'login'); - } + if (!$result->active) { + $this->session->alert(lang('account_disabled'), 'danger'); + return redirect($response, route('login')); + } - if (!$result->active) { - $this->session->alert(lang('account_disabled'), 'danger'); - return redirect($response, 'login'); - } + $this->session->set('logged', true); + $this->session->set('user_id', $result->id); + $this->session->set('username', $result->username); + $this->session->set('admin', $result->is_admin); + $this->session->set('used_space', humanFileSize($this->getUsedSpaceByUser($result->id))); - $this->session->set('logged', true); - $this->session->set('user_id', $result->id); - $this->session->set('username', $result->username); - $this->session->set('admin', $result->is_admin); - $this->session->set('used_space', humanFileSize($this->getUsedSpaceByUser($result->id))); + $this->session->alert(lang('welcome', [$result->username]), 'info'); + $this->logger->info("User $result->username logged in."); - $this->session->alert(lang('welcome', [$result->username]), 'info'); - $this->logger->info("User $result->username logged in."); + if ($this->session->has('redirectTo')) { + return redirect($response, $this->session->get('redirectTo')); + } - if ($this->session->has('redirectTo')) { - return $response->withRedirect($this->session->get('redirectTo')); - } + return redirect($response, route('home')); + } - return redirect($response, 'home'); - } - - /** - * @param Request $request - * @param Response $response - * @return Response - */ - public function logout(Request $request, Response $response): Response - { - $this->session->clear(); - $this->session->set('logged', false); - $this->session->alert(lang('goodbye'), 'warning'); - return redirect($response, 'login.show'); - } + /** + * @param Response $response + * @return Response + */ + public function logout(Response $response): Response + { + $this->session->clear(); + $this->session->set('logged', false); + $this->session->alert(lang('goodbye'), 'warning'); + return redirect($response, route('login.show')); + } } \ No newline at end of file diff --git a/app/Controllers/ThemeController.php b/app/Controllers/ThemeController.php index d7c5f6a..851605b 100644 --- a/app/Controllers/ThemeController.php +++ b/app/Controllers/ThemeController.php @@ -2,40 +2,42 @@ namespace App\Controllers; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; class ThemeController extends Controller { - /** - * @param Request $request - * @param Response $response - * @return Response - */ - public function getThemes(Request $request, Response $response): Response - { - $apiJson = json_decode(file_get_contents('https://bootswatch.com/api/4.json')); + /** + * @param Response $response + * @return Response + */ + public function getThemes(Response $response): Response + { + $apiJson = json_decode(file_get_contents('https://bootswatch.com/api/4.json')); - $out = []; + $out = []; - $out['Default - Bootstrap 4 default theme'] = 'https://bootswatch.com/_vendor/bootstrap/dist/css/bootstrap.min.css'; - foreach ($apiJson->themes as $theme) { - $out["{$theme->name} - {$theme->description}"] = $theme->cssMin; - } + $out['Default - Bootstrap 4 default theme'] = 'https://bootswatch.com/_vendor/bootstrap/dist/css/bootstrap.min.css'; + foreach ($apiJson->themes as $theme) { + $out["{$theme->name} - {$theme->description}"] = $theme->cssMin; + } - return $response->withJson($out); - } + return json($response, $out); + } + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function applyTheme(Request $request, Response $response): Response + { + if (!is_writable(BASE_DIR.'static/bootstrap/css/bootstrap.min.css')) { + $this->session->alert(lang('cannot_write_file'), 'danger'); + return redirect($response, route('system')); + } - public function applyTheme(Request $request, Response $response): Response - { - if (!is_writable(BASE_DIR . 'static/bootstrap/css/bootstrap.min.css')) { - $this->session->alert(lang('cannot_write_file'), 'danger'); - return redirect($response, 'system'); - } - - file_put_contents(BASE_DIR . 'static/bootstrap/css/bootstrap.min.css', file_get_contents($request->getParam('css'))); - return redirect($response, 'system'); - } - + file_put_contents(BASE_DIR.'static/bootstrap/css/bootstrap.min.css', file_get_contents(param($request, 'css'))); + return redirect($response, route('system')); + } } \ No newline at end of file diff --git a/app/Controllers/UpgradeController.php b/app/Controllers/UpgradeController.php index 22a3e64..f5b6a12 100644 --- a/app/Controllers/UpgradeController.php +++ b/app/Controllers/UpgradeController.php @@ -3,8 +3,9 @@ namespace App\Controllers; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use RuntimeException; use ZipArchive; class UpgradeController extends Controller @@ -12,39 +13,38 @@ class UpgradeController extends Controller const GITHUB_SOURCE_API = 'https://api.github.com/repos/SergiX44/XBackBone/releases'; /** - * @param Request $request * @param Response $response * @return Response */ - public function upgrade(Request $request, Response $response): Response + public function upgrade(Response $response): Response { if (!is_writable(BASE_DIR)) { $this->session->alert(lang('path_not_writable', BASE_DIR), 'warning'); - return redirect($response, 'system'); + return redirect($response, route('system')); } try { $json = $this->getApiJson(); - } catch (\RuntimeException $e) { + } catch (RuntimeException $e) { $this->session->alert($e->getMessage(), 'danger'); - return redirect($response, 'system'); + return redirect($response, route('system')); } if (version_compare($json[0]->tag_name, PLATFORM_VERSION, '<=')) { $this->session->alert(lang('already_latest_version'), 'warning'); - return redirect($response, 'system'); + return redirect($response, route('system')); } $tmpFile = sys_get_temp_dir().DIRECTORY_SEPARATOR.'xbackbone_update.zip'; if (file_put_contents($tmpFile, file_get_contents($json[0]->assets[0]->browser_download_url)) === false) { $this->session->alert(lang('cannot_retrieve_file'), 'danger'); - return redirect($response, 'system'); + return redirect($response, route('system')); }; if (filesize($tmpFile) !== $json[0]->assets[0]->size) { $this->session->alert(lang('file_size_no_match'), 'danger'); - return redirect($response, 'system'); + return redirect($response, route('system')); } $config = require BASE_DIR.'config.php'; @@ -85,7 +85,7 @@ class UpgradeController extends Controller $updateZip->close(); unlink($tmpFile); - return redirect($response, '/install'); + return redirect($response, urlFor('/install')); } /** @@ -101,7 +101,7 @@ class UpgradeController extends Controller 'upgrade' => false, ]; - $acceptPrerelease = $request->getParam('prerelease', 'false') === 'true'; + $acceptPrerelease = param($request, 'prerelease', 'false') === 'true'; try { $json = $this->getApiJson(); @@ -120,11 +120,12 @@ class UpgradeController extends Controller break; } } - } catch (\RuntimeException $e) { + } catch (RuntimeException $e) { $jsonResponse['status'] = 'ERROR'; $jsonResponse['message'] = $e->getMessage(); } - return $response->withJson($jsonResponse); + + return json($response, $jsonResponse); } protected function getApiJson() @@ -142,7 +143,7 @@ class UpgradeController extends Controller $data = @file_get_contents(self::GITHUB_SOURCE_API, false, stream_context_create($opts)); if ($data === false) { - throw new \RuntimeException('Cannot contact the Github API. Try again.'); + throw new RuntimeException('Cannot contact the Github API. Try again.'); } return json_decode($data); diff --git a/app/Controllers/UploadController.php b/app/Controllers/UploadController.php index eb71a2d..e81ec42 100644 --- a/app/Controllers/UploadController.php +++ b/app/Controllers/UploadController.php @@ -2,411 +2,419 @@ namespace App\Controllers; -use App\Exceptions\UnauthorizedException; +use GuzzleHttp\Psr7\Stream; use Intervention\Image\ImageManagerStatic as Image; 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; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Slim\Exception\HttpNotFoundException; +use Slim\Exception\HttpUnauthorizedException; class UploadController extends Controller { - /** - * @param Request $request - * @param Response $response - * @return Response - * @throws FileExistsException - */ - public function upload(Request $request, Response $response): Response - { - - $json = [ - 'message' => null, - 'version' => PLATFORM_VERSION, - ]; - - if ($this->settings['maintenance']) { - $json['message'] = 'Endpoint under maintenance.'; - return $response->withJson($json, 503); - } - - if ($request->getServerParam('CONTENT_LENGTH') > stringToBytes(ini_get('post_max_size'))) { - $json['message'] = 'File too large (post_max_size too low?).'; - return $response->withJson($json, 400); - } - - if (isset($request->getUploadedFiles()['upload']) && $request->getUploadedFiles()['upload']->getError() === UPLOAD_ERR_INI_SIZE) { - $json['message'] = 'File too large (upload_max_filesize too low?).'; - return $response->withJson($json, 400); - } - - if ($request->getParam('token') === null) { - $json['message'] = 'Token not specified.'; - return $response->withJson($json, 400); - } - - $user = $this->database->query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', $request->getParam('token'))->fetch(); - - if (!$user) { - $json['message'] = 'Token specified not found.'; - return $response->withJson($json, 404); - } - - if (!$user->active) { - $json['message'] = 'Account disabled.'; - return $response->withJson($json, 401); - } - - do { - $code = humanRandomString(); - } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0); - - /** @var \Psr\Http\Message\UploadedFileInterface $file */ - $file = $request->getUploadedFiles()['upload']; - - $fileInfo = pathinfo($file->getClientFilename()); - $storagePath = "$user->user_code/$code.$fileInfo[extension]"; - - $this->storage->writeStream($storagePath, $file->getStream()->detach()); - - $this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`) VALUES (?, ?, ?, ?)', [ - $user->id, - $code, - $file->getClientFilename(), - $storagePath, - ]); - - $json['message'] = 'OK.'; - $json['url'] = urlFor("/$user->user_code/$code.$fileInfo[extension]"); - - $this->logger->info("User $user->username uploaded new media.", [$this->database->raw()->lastInsertId()]); - - return $response->withJson($json, 201); - } - - /** - * @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($args['userCode'], $args['mediaCode']); - - if (!$media || (!$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get('admin', false))) { - throw new NotFoundException($request, $response); - } - - $filesystem = $this->storage; - - if (isBot($request->getHeaderLine('User-Agent'))) { - return $this->streamMedia($request, $response, $filesystem, $media); - } else { - try { - $media->mimetype = $filesystem->getMimetype($media->storage_path); - $size = $filesystem->getSize($media->storage_path); - - $type = explode('/', $media->mimetype)[0]; - if ($type === 'image' && !isDisplayableImage($media->mimetype)) { - $type = 'application'; - $media->mimetype = 'application/octet-stream'; - } - if ($type === 'text') { - if ($size <= (200 * 1024)) { // less than 200 KB - $media->text = $filesystem->read($media->storage_path); - } else { - $type = 'application'; - $media->mimetype = 'application/octet-stream'; - } - } - $media->size = humanFileSize($size); - - } catch (FileNotFoundException $e) { - throw new NotFoundException($request, $response); - } - - return $this->view->render($response, 'upload/public.twig', [ - 'delete_token' => isset($args['token']) ? $args['token'] : null, - 'media' => $media, - 'type' => $type, - 'extension' => pathinfo($media->filename, PATHINFO_EXTENSION), - ]); - } - } - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - * @throws NotFoundException - * @throws UnauthorizedException - */ - public function deleteByToken(Request $request, Response $response, $args): Response - { - $media = $this->getMedia($args['userCode'], $args['mediaCode']); - - if (!$media) { - throw new NotFoundException($request, $response); - } - - $user = $this->database->query('SELECT `id`, `active` FROM `users` WHERE `token` = ? LIMIT 1', $args['token'])->fetch(); - - if (!$user) { - $this->session->alert(lang('token_not_found'), 'danger'); - return $response->withRedirect($request->getHeaderLine('HTTP_REFERER')); - } - - if (!$user->active) { - $this->session->alert(lang('account_disabled'), 'danger'); - return $response->withRedirect($request->getHeaderLine('HTTP_REFERER')); - } - - if ($this->session->get('admin', false) || $user->id === $media->user_id) { - - try { - $this->storage->delete($media->storage_path); - } catch (FileNotFoundException $e) { - throw new NotFoundException($request, $response); - } finally { - $this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $media->mediaId); - $this->logger->info('User ' . $user->username . ' deleted a media via token.', [$media->mediaId]); - } - } else { - throw new UnauthorizedException(); - } - - return redirect($response, 'home'); - } - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - * @throws NotFoundException - * @throws FileNotFoundException - */ - public function getRawById(Request $request, Response $response, $args): Response - { - - $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); - - if (!$media) { - throw new NotFoundException($request, $response); - } - - return $this->streamMedia($request, $response, $this->storage, $media); - } - - /** - * @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($args['userCode'], $args['mediaCode']); - - if (!$media || !$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get('admin', false)) { - throw new NotFoundException($request, $response); - } - return $this->streamMedia($request, $response, $this->storage, $media); - } - - - /** - * @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($args['userCode'], $args['mediaCode']); - - if (!$media || !$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get('admin', false)) { - throw new NotFoundException($request, $response); - } - return $this->streamMedia($request, $response, $this->storage, $media, 'attachment'); - } - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - * @throws NotFoundException - */ - public function togglePublish(Request $request, Response $response, $args): Response - { - if ($this->session->get('admin')) { - $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); - } else { - $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? AND `user_id` = ? LIMIT 1', [$args['id'], $this->session->get('user_id')])->fetch(); - } - - if (!$media) { - throw new NotFoundException($request, $response); - } - - $this->database->query('UPDATE `uploads` SET `published`=? WHERE `id`=?', [$media->published ? 0 : 1, $media->id]); - - return $response->withStatus(200); - } - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - * @throws NotFoundException - * @throws UnauthorizedException - */ - public function delete(Request $request, Response $response, $args): Response - { - $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); - - if (!$media) { - throw new NotFoundException($request, $response); - } - - if ($this->session->get('admin', false) || $media->user_id === $this->session->get('user_id')) { - - try { - $this->storage->delete($media->storage_path); - } catch (FileNotFoundException $e) { - throw new NotFoundException($request, $response); - } finally { - $this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $args['id']); - $this->logger->info('User ' . $this->session->get('username') . ' deleted a media.', [$args['id']]); - $this->session->set('used_space', humanFileSize($this->getUsedSpaceByUser($this->session->get('user_id')))); - } - } else { - throw new UnauthorizedException(); - } - - return $response->withStatus(200); - } - - /** - * @param $userCode - * @param $mediaCode - * @return mixed - */ - protected function getMedia($userCode, $mediaCode) - { - $mediaCode = pathinfo($mediaCode)['filename']; - - $media = $this->database->query('SELECT `uploads`.*, `users`.*, `users`.`id` AS `userId`, `uploads`.`id` AS `mediaId` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1', [ - $userCode, - $mediaCode, - ])->fetch(); - - return $media; - } - - /** - * @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 - { - set_time_limit(0); - $mime = $storage->getMimetype($media->storage_path); - - if ($request->getParam('width') !== null && explode('/', $mime)[0] === 'image') { - - $image = Image::make($storage->readStream($media->storage_path)) - ->resizeCanvas( - $request->getParam('width'), - $request->getParam('height'), - 'center') - ->encode('png'); - - return $response - ->withHeader('Content-Type', 'image/png') - ->withHeader('Content-Disposition', $disposition . ';filename="scaled-' . pathinfo($media->filename)['filename'] . '.png"') - ->write($image); - } else { - $stream = new Stream($storage->readStream($media->storage_path)); - - if (!in_array(explode('/', $mime)[0], ['image', 'video', 'audio']) || $disposition === 'attachment') { - return $response->withHeader('Content-Type', $mime) - ->withHeader('Content-Disposition', $disposition . '; filename="' . $media->filename . '"') - ->withHeader('Content-Length', $stream->getSize()) - ->withBody($stream); - } - - $end = $stream->getSize() - 1; - - if ($request->getServerParam('HTTP_RANGE') !== null) { - list(, $range) = explode('=', $request->getServerParam('HTTP_RANGE'), 2); - - if (strpos($range, ',') !== false) { - return $response->withHeader('Content-Type', $mime) - ->withHeader('Content-Disposition', $disposition . '; filename="' . $media->filename . '"') - ->withHeader('Content-Length', $stream->getSize()) - ->withHeader('Accept-Ranges', 'bytes') - ->withHeader('Content-Range', "0,{$stream->getSize()}") - ->withStatus(416) - ->withBody($stream); - } - - if ($range === '-') { - $start = $stream->getSize() - (int)substr($range, 1); - } else { - $range = explode('-', $range); - $start = (int)$range[0]; - $end = (isset($range[1]) && is_numeric($range[1])) ? (int)$range[1] : $stream->getSize(); - } - - $end = ($end > $stream->getSize() - 1) ? $stream->getSize() - 1 : $end; - $stream->seek($start); - - header("Content-Type: $mime"); - header('Content-Length: ' . ($end - $start + 1)); - header('Accept-Ranges: bytes'); - header("Content-Range: bytes $start-$end/{$stream->getSize()}"); - - http_response_code(206); - ob_end_clean(); - - $buffer = 16348; - $readed = $start; - while ($readed < $end) { - if ($readed + $buffer > $end) { - $buffer = $end - $readed + 1; - } - echo $stream->read($buffer); - $readed += $buffer; - } - - exit(0); - } - - return $response->withHeader('Content-Type', $mime) - ->withHeader('Content-Length', $stream->getSize()) - ->withHeader('Accept-Ranges', 'bytes') - ->withStatus(200) - ->withBody($stream); - } - } + /** + * @param Request $request + * @param Response $response + * @return Response + * @throws FileExistsException + */ + public function upload(Request $request, Response $response): Response + { + + $json = [ + 'message' => null, + 'version' => PLATFORM_VERSION, + ]; + + if ($this->config['maintenance']) { + $json['message'] = 'Endpoint under maintenance.'; + return json($response, $json, 503); + } + + if ($request->getServerParams()['CONTENT_LENGTH'] > stringToBytes(ini_get('post_max_size'))) { + $json['message'] = 'File too large (post_max_size too low?).'; + return json($response, $json, 400); + } + + if (isset($request->getUploadedFiles()['upload']) && $request->getUploadedFiles()['upload']->getError() === UPLOAD_ERR_INI_SIZE) { + $json['message'] = 'File too large (upload_max_filesize too low?).'; + return json($response, $json, 400); + } + + if (param($request, 'token') === null) { + $json['message'] = 'Token not specified.'; + return json($response, $json, 400); + } + + $user = $this->database->query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', param($request, 'token'))->fetch(); + + if (!$user) { + $json['message'] = 'Token specified not found.'; + return json($response, $json, 404); + } + + if (!$user->active) { + $json['message'] = 'Account disabled.'; + return json($response, $json, 401); + } + + do { + $code = humanRandomString(); + } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0); + + /** @var \Psr\Http\Message\UploadedFileInterface $file */ + $file = $request->getUploadedFiles()['upload']; + + $fileInfo = pathinfo($file->getClientFilename()); + $storagePath = "$user->user_code/$code.$fileInfo[extension]"; + + $this->storage->writeStream($storagePath, $file->getStream()->detach()); + + $this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`) VALUES (?, ?, ?, ?)', [ + $user->id, + $code, + $file->getClientFilename(), + $storagePath, + ]); + + $json['message'] = 'OK.'; + $json['url'] = urlFor("/$user->user_code/$code.$fileInfo[extension]"); + + $this->logger->info("User $user->username uploaded new media.", [$this->database->raw()->lastInsertId()]); + + return json($response, $json, 201); + } + + /** + * @param Request $request + * @param Response $response + * @param string $userCode + * @param string $mediaCode + * @param string|null $token + * @return Response + * @throws HttpNotFoundException + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + * @throws FileNotFoundException + */ + public function show(Request $request, Response $response, string $userCode, string $mediaCode, string $token = null): Response + { + $media = $this->getMedia($userCode, $mediaCode); + + if (!$media || (!$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get('admin', false))) { + throw new HttpNotFoundException($request); + } + + $filesystem = $this->storage; + + if (isBot($request->getHeaderLine('User-Agent'))) { + return $this->streamMedia($request, $response, $filesystem, $media); + } else { + try { + $media->mimetype = $filesystem->getMimetype($media->storage_path); + $size = $filesystem->getSize($media->storage_path); + + $type = explode('/', $media->mimetype)[0]; + if ($type === 'image' && !isDisplayableImage($media->mimetype)) { + $type = 'application'; + $media->mimetype = 'application/octet-stream'; + } + if ($type === 'text') { + if ($size <= (200 * 1024)) { // less than 200 KB + $media->text = $filesystem->read($media->storage_path); + } else { + $type = 'application'; + $media->mimetype = 'application/octet-stream'; + } + } + $media->size = humanFileSize($size); + + } catch (FileNotFoundException $e) { + throw new HttpNotFoundException($request); + } + + return view()->render($response, 'upload/public.twig', [ + 'delete_token' => $token, + 'media' => $media, + 'type' => $type, + 'extension' => pathinfo($media->filename, PATHINFO_EXTENSION), + ]); + } + } + + /** + * @param Request $request + * @param Response $response + * @param string $userCode + * @param string $mediaCode + * @param string $token + * @return Response + * @throws HttpNotFoundException + * @throws HttpUnauthorizedException + */ + public function deleteByToken(Request $request, Response $response, string $userCode, string $mediaCode, string $token): Response + { + $media = $this->getMedia($userCode, $mediaCode); + + if (!$media) { + throw new HttpNotFoundException($request); + } + + $user = $this->database->query('SELECT `id`, `active` FROM `users` WHERE `token` = ? LIMIT 1', $token)->fetch(); + + if (!$user) { + $this->session->alert(lang('token_not_found'), 'danger'); + return redirect($response, $request->getHeaderLine('HTTP_REFERER')); + } + + if (!$user->active) { + $this->session->alert(lang('account_disabled'), 'danger'); + return redirect($response, $request->getHeaderLine('HTTP_REFERER')); + } + + if ($this->session->get('admin', false) || $user->id === $media->user_id) { + + try { + $this->storage->delete($media->storage_path); + } catch (FileNotFoundException $e) { + throw new HttpNotFoundException($request); + } finally { + $this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $media->mediaId); + $this->logger->info('User '.$user->username.' deleted a media via token.', [$media->mediaId]); + } + } else { + throw new HttpUnauthorizedException($request); + } + + return redirect($response, route('home')); + } + + /** + * @param Request $request + * @param Response $response + * @param int $id + * @return Response + * @throws FileNotFoundException + * @throws HttpNotFoundException + */ + public function getRawById(Request $request, Response $response, int $id): Response + { + + $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch(); + + if (!$media) { + throw new HttpNotFoundException($request); + } + + return $this->streamMedia($request, $response, $this->storage, $media); + } + + /** + * @param Request $request + * @param Response $response + * @param string $userCode + * @param string $mediaCode + * @return Response + * @throws FileNotFoundException + * @throws HttpNotFoundException + */ + public function showRaw(Request $request, Response $response, string $userCode, string $mediaCode): Response + { + $media = $this->getMedia($userCode, $mediaCode); + + if (!$media || !$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get('admin', false)) { + throw new HttpNotFoundException($request); + } + return $this->streamMedia($request, $response, $this->storage, $media); + } + + + /** + * @param Request $request + * @param Response $response + * @param string $userCode + * @param string $mediaCode + * @return Response + * @throws FileNotFoundException + * @throws HttpNotFoundException + */ + public function download(Request $request, Response $response, string $userCode, string $mediaCode): Response + { + $media = $this->getMedia($userCode, $mediaCode); + + if (!$media || !$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get('admin', false)) { + throw new HttpNotFoundException($request); + } + return $this->streamMedia($request, $response, $this->storage, $media, 'attachment'); + } + + /** + * @param Request $request + * @param Response $response + * @param int $id + * @return Response + * @throws HttpNotFoundException + */ + public function togglePublish(Request $request, Response $response, int $id): Response + { + if ($this->session->get('admin')) { + $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch(); + } else { + $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? AND `user_id` = ? LIMIT 1', [$id, $this->session->get('user_id')])->fetch(); + } + + if (!$media) { + throw new HttpNotFoundException($request); + } + + $this->database->query('UPDATE `uploads` SET `published`=? WHERE `id`=?', [$media->published ? 0 : 1, $media->id]); + + return $response->withStatus(200); + } + + /** + * @param Request $request + * @param Response $response + * @param int $id + * @return Response + * @throws HttpNotFoundException + * @throws HttpUnauthorizedException + */ + public function delete(Request $request, Response $response, int $id): Response + { + $media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch(); + + if (!$media) { + throw new HttpNotFoundException($request); + } + + if ($this->session->get('admin', false) || $media->user_id === $this->session->get('user_id')) { + + try { + $this->storage->delete($media->storage_path); + } catch (FileNotFoundException $e) { + throw new HttpNotFoundException($request); + } finally { + $this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $id); + $this->logger->info('User '.$this->session->get('username').' deleted a media.', [$id]); + $this->session->set('used_space', humanFileSize($this->getUsedSpaceByUser($this->session->get('user_id')))); + } + } else { + throw new HttpUnauthorizedException($request); + } + + return $response->withStatus(200); + } + + /** + * @param $userCode + * @param $mediaCode + * @return mixed + */ + protected function getMedia($userCode, $mediaCode) + { + $mediaCode = pathinfo($mediaCode)['filename']; + + $media = $this->database->query('SELECT `uploads`.*, `users`.*, `users`.`id` AS `userId`, `uploads`.`id` AS `mediaId` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1', [ + $userCode, + $mediaCode, + ])->fetch(); + + return $media; + } + + /** + * @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 + { + set_time_limit(0); + $mime = $storage->getMimetype($media->storage_path); + + if (param($request, 'width') !== null && explode('/', $mime)[0] === 'image') { + + $image = Image::make($storage->readStream($media->storage_path)) + ->resizeCanvas( + param($request, 'width'), + param($request, 'height'), + 'center') + ->encode('png'); + + $response->getBody()->write($image); + return $response + ->withHeader('Content-Type', 'image/png') + ->withHeader('Content-Disposition', $disposition.';filename="scaled-'.pathinfo($media->filename)['filename'].'.png"'); + } else { + $stream = new Stream($storage->readStream($media->storage_path)); + + if (!in_array(explode('/', $mime)[0], ['image', 'video', 'audio']) || $disposition === 'attachment') { + return $response->withHeader('Content-Type', $mime) + ->withHeader('Content-Disposition', $disposition.'; filename="'.$media->filename.'"') + ->withHeader('Content-Length', $stream->getSize()) + ->withBody($stream); + } + + $end = $stream->getSize() - 1; + if (isset($request->getServerParams()['HTTP_RANGE'])) { + list(, $range) = explode('=', $request->getServerParams()['HTTP_RANGE'], 2); + + if (strpos($range, ',') !== false) { + return $response->withHeader('Content-Type', $mime) + ->withHeader('Content-Disposition', $disposition.'; filename="'.$media->filename.'"') + ->withHeader('Content-Length', $stream->getSize()) + ->withHeader('Accept-Ranges', 'bytes') + ->withHeader('Content-Range', "0,{$stream->getSize()}") + ->withStatus(416) + ->withBody($stream); + } + + if ($range === '-') { + $start = $stream->getSize() - (int)substr($range, 1); + } else { + $range = explode('-', $range); + $start = (int)$range[0]; + $end = (isset($range[1]) && is_numeric($range[1])) ? (int)$range[1] : $stream->getSize(); + } + + $end = ($end > $stream->getSize() - 1) ? $stream->getSize() - 1 : $end; + $stream->seek($start); + + header("Content-Type: $mime"); + header('Content-Length: '.($end - $start + 1)); + header('Accept-Ranges: bytes'); + header("Content-Range: bytes $start-$end/{$stream->getSize()}"); + + http_response_code(206); + ob_end_clean(); + + $buffer = 16348; + $readed = $start; + while ($readed < $end) { + if ($readed + $buffer > $end) { + $buffer = $end - $readed + 1; + } + echo $stream->read($buffer); + $readed += $buffer; + } + + exit(0); + } + + return $response->withHeader('Content-Type', $mime) + ->withHeader('Content-Length', $stream->getSize()) + ->withHeader('Accept-Ranges', 'bytes') + ->withStatus(200) + ->withBody($stream); + } + } } \ No newline at end of file diff --git a/app/Controllers/UserController.php b/app/Controllers/UserController.php index f6ebf45..dba503d 100644 --- a/app/Controllers/UserController.php +++ b/app/Controllers/UserController.php @@ -3,419 +3,430 @@ namespace App\Controllers; -use App\Exceptions\UnauthorizedException; -use Slim\Exception\NotFoundException; -use Slim\Http\Request; -use Slim\Http\Response; +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 { - const PER_PAGE = 15; - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - */ - public function index(Request $request, Response $response, $args): Response - { - $page = isset($args['page']) ? (int)$args['page'] : 0; - $page = max(0, --$page); - - $users = $this->database->query('SELECT * FROM `users` LIMIT ? OFFSET ?', [self::PER_PAGE, $page * self::PER_PAGE])->fetchAll(); - - $pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count / self::PER_PAGE; - - return $this->view->render($response, - 'user/index.twig', - [ - 'users' => $users, - 'next' => $page < floor($pages), - 'previous' => $page >= 1, - 'current_page' => ++$page, - ] - ); - } - - /** - * @param Request $request - * @param Response $response - * @return Response - */ - public function create(Request $request, Response $response): Response - { - return $this->view->render($response, 'user/create.twig'); - } - - /** - * @param Request $request - * @param Response $response - * @return Response - */ - public function store(Request $request, Response $response): Response - { - if ($request->getParam('email') === null) { - $this->session->alert(lang('email_required'), 'danger'); - return redirect($response, 'user.create'); - } - - if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ?', $request->getParam('email'))->fetch()->count > 0) { - $this->session->alert(lang('email_taken'), 'danger'); - return redirect($response, 'user.create'); - } - - if ($request->getParam('username') === null) { - $this->session->alert(lang('username_required'), 'danger'); - return redirect($response, 'user.create'); - } - - if ($request->getParam('password') === null) { - $this->session->alert(lang('password_required'), 'danger'); - return redirect($response, 'user.create'); - } - - if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', $request->getParam('username'))->fetch()->count > 0) { - $this->session->alert(lang('username_taken'), 'danger'); - return redirect($response, 'user.create'); - } - - do { - $userCode = humanRandomString(5); - } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0); - - $token = $this->generateNewToken(); - - $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 ? 1 : 0, - $request->getParam('is_active') !== null ? 1 : 0, - $userCode, - $token, - ]); - - $this->session->alert(lang('user_created', [$request->getParam('username')]), 'success'); - $this->logger->info('User ' . $this->session->get('username') . ' created a new user.', [array_diff_key($request->getParams(), array_flip(['password']))]); - - return redirect($response, 'user.index'); - } - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - * @throws NotFoundException - */ - public function edit(Request $request, Response $response, $args): Response - { - $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); - - if (!$user) { - throw new NotFoundException($request, $response); - } - - return $this->view->render($response, 'user/edit.twig', [ - 'profile' => false, - 'user' => $user, - ]); - } - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - * @throws NotFoundException - */ - public function update(Request $request, Response $response, $args): Response - { - $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); - - if (!$user) { - throw new NotFoundException($request, $response); - } - - if ($request->getParam('email') === null) { - $this->session->alert(lang('email_required'), 'danger'); - return redirect($response, 'user.edit', ['id' => $args['id']]); - } - - if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ? AND `email` <> ?', [$request->getParam('email'), $user->email])->fetch()->count > 0) { - $this->session->alert(lang('email_taken'), 'danger'); - return redirect($response, 'user.edit', ['id' => $args['id']]); - } - - if ($request->getParam('username') === null) { - $this->session->alert(lang('username_required'), 'danger'); - return redirect($response, 'user.edit', ['id' => $args['id']]); - } - - if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [$request->getParam('username'), $user->username])->fetch()->count > 0) { - $this->session->alert(lang('username_taken'), 'danger'); - return redirect($response, 'user.edit', ['id' => $args['id']]); - } - - if ($user->id === $this->session->get('user_id') && $request->getParam('is_admin') === null) { - $this->session->alert(lang('cannot_demote'), 'danger'); - return redirect($response, 'user.edit', ['id' => $args['id']]); - } - - 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 ? 1 : 0, - $request->getParam('is_active') !== null ? 1 : 0, - $user->id, - ]); - } else { - $this->database->query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [ - $request->getParam('email'), - $request->getParam('username'), - $request->getParam('is_admin') !== null ? 1 : 0, - $request->getParam('is_active') !== null ? 1 : 0, - $user->id, - ]); - } - - $this->session->alert(lang('user_updated', [$request->getParam('username')]), 'success'); - $this->logger->info('User ' . $this->session->get('username') . " updated $user->id.", [ - array_diff_key((array)$user, array_flip(['password'])), - array_diff_key($request->getParams(), array_flip(['password'])), - ]); - - return redirect($response, 'user.index'); - - } - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - * @throws NotFoundException - */ - public function delete(Request $request, Response $response, $args): Response - { - $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); - - if (!$user) { - throw new NotFoundException($request, $response); - } - - if ($user->id === $this->session->get('user_id')) { - $this->session->alert(lang('cannot_delete'), 'danger'); - return redirect($response, 'user.index'); - } - - $this->database->query('DELETE FROM `users` WHERE `id` = ?', $user->id); - - $this->session->alert(lang('user_deleted'), 'success'); - $this->logger->info('User ' . $this->session->get('username') . " deleted $user->id."); - - return redirect($response, 'user.index'); - } - - /** - * @param Request $request - * @param Response $response - * @return Response - * @throws NotFoundException - * @throws UnauthorizedException - */ - public function profile(Request $request, Response $response): Response - { - $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $this->session->get('user_id'))->fetch(); - - if (!$user) { - throw new NotFoundException($request, $response); - } - - if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) { - throw new UnauthorizedException(); - } - - return $this->view->render($response, 'user/edit.twig', [ - 'profile' => true, - 'user' => $user, - ]); - } - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - * @throws NotFoundException - * @throws UnauthorizedException - */ - public function profileEdit(Request $request, Response $response, $args): Response - { - $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); - - if (!$user) { - throw new NotFoundException($request, $response); - } - - if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) { - throw new UnauthorizedException(); - } - - if ($request->getParam('email') === null) { - $this->session->alert(lang('email_required'), 'danger'); - return redirect($response, 'profile'); - } - - if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ? AND `email` <> ?', [$request->getParam('email'), $user->email])->fetch()->count > 0) { - $this->session->alert(lang('email_taken'), 'danger'); - return redirect($response, 'profile'); - } - - 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 { - $this->database->query('UPDATE `users` SET `email`=? WHERE `id` = ?', [ - $request->getParam('email'), - $user->id, - ]); - } - - $this->session->alert(lang('profile_updated'), 'success'); - $this->logger->info('User ' . $this->session->get('username') . " updated profile of $user->id."); - - return redirect($response, 'profile'); - } - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - * @throws NotFoundException - * @throws UnauthorizedException - */ - public function refreshToken(Request $request, Response $response, $args): Response - { - $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); - - if (!$user) { - throw new NotFoundException($request, $response); - } - - if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) { - throw new UnauthorizedException(); - } - - $token = $this->generateNewToken(); - - $this->database->query('UPDATE `users` SET `token`=? WHERE `id` = ?', [ - $token, - $user->id, - ]); - - $this->logger->info('User ' . $this->session->get('username') . " refreshed token of user $user->id."); - - $response->getBody()->write($token); - - return $response; - } - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - * @throws NotFoundException - * @throws UnauthorizedException - */ - public function getShareXconfigFile(Request $request, Response $response, $args): Response - { - $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); - - if (!$user) { - throw new NotFoundException($request, $response); - } - - if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) { - throw new UnauthorizedException(); - } - - if ($user->token === null || $user->token === '') { - $this->session->alert('You don\'t have a personal upload token. (Click the update token button and try again)', 'danger'); - return $response->withRedirect($request->getHeaderLine('HTTP_REFERER')); - } - - $json = [ - 'DestinationType' => 'ImageUploader, TextUploader, FileUploader', - 'RequestURL' => route('upload'), - 'FileFormName' => 'upload', - 'Arguments' => [ - 'file' => '$filename$', - 'text' => '$input$', - 'token' => $user->token, - ], - 'URL' => '$json:url$', - 'ThumbnailURL' => '$json:url$/raw', - 'DeletionURL' => '$json:url$/delete/' . $user->token, - ]; - - return $response - ->withHeader('Content-Disposition', 'attachment;filename="' . $user->username . '-ShareX.sxcu"') - ->withJson($json, 200, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); - } - - /** - * @param Request $request - * @param Response $response - * @param $args - * @return Response - * @throws NotFoundException - * @throws UnauthorizedException - */ - public function getUploaderScriptFile(Request $request, Response $response, $args): Response - { - $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch(); - - if (!$user) { - throw new NotFoundException($request, $response); - } - - if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) { - throw new UnauthorizedException(); - } - - if ($user->token === null || $user->token === '') { - $this->session->alert('You don\'t have a personal upload token. (Click the update token button and try again)', 'danger'); - return $response->withRedirect($request->getHeaderLine('HTTP_REFERER')); - } - - return $this->view->render($response->withHeader('Content-Disposition', 'attachment;filename="xbackbone_uploader_' . $user->username . '.sh"'), - 'scripts/xbackbone_uploader.sh.twig', - [ - 'username' => $user->username, - 'upload_url' => route('upload'), - 'token' => $user->token, - ] - ); - } - - /** - * @return string - */ - protected function generateNewToken(): 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; - } + const PER_PAGE = 15; + + /** + * @param Response $response + * @param int|null $page + * @return Response + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function index(Response $response, int $page = 0): Response + { + $page = max(0, --$page); + + $users = $this->database->query('SELECT * FROM `users` LIMIT ? OFFSET ?', [self::PER_PAGE, $page * self::PER_PAGE])->fetchAll(); + + $pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count / self::PER_PAGE; + + return view()->render($response, + 'user/index.twig', + [ + 'users' => $users, + 'next' => $page < floor($pages), + 'previous' => $page >= 1, + 'current_page' => ++$page, + ] + ); + } + + /** + * @param Response $response + * @return Response + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function create(Response $response): Response + { + return view()->render($response, 'user/create.twig'); + } + + /** + * @param Request $request + * @param Response $response + * @return Response + */ + public function store(Request $request, Response $response): Response + { + if (param($request, 'email') === null) { + $this->session->alert(lang('email_required'), 'danger'); + return redirect($response, route('user.create')); + } + + 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 (param($request, 'username') === null) { + $this->session->alert(lang('username_required'), 'danger'); + return redirect($response, route('user.create')); + } + + if (param($request, 'password') === null) { + $this->session->alert(lang('password_required'), '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'); + return redirect($response, route('user.create')); + } + + do { + $userCode = humanRandomString(5); + } while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0); + + $token = $this->generateNewToken(); + + $this->database->query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`) VALUES (?, ?, ?, ?, ?, ?, ?)', [ + 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, + $userCode, + $token, + ]); + + $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']))]); + + return redirect($response, route('user.index')); + } + + /** + * @param Request $request + * @param Response $response + * @param $id + * @return Response + * @throws HttpNotFoundException + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function edit(Request $request, Response $response, int $id): Response + { + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + + if (!$user) { + throw new HttpNotFoundException($request); + } + + return view()->render($response, 'user/edit.twig', [ + 'profile' => false, + 'user' => $user, + ]); + } + + /** + * @param Request $request + * @param Response $response + * @param int $id + * @return Response + * @throws HttpNotFoundException + */ + public function update(Request $request, Response $response, int $id): Response + { + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + + if (!$user) { + throw new HttpNotFoundException($request); + } + + if (param($request, 'email') === null) { + $this->session->alert(lang('email_required'), 'danger'); + return redirect($response, route('user.edit', ['id' => $id])); + } + + 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 (param($request, 'username') === null) { + $this->session->alert(lang('username_required'), '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'); + return redirect($response, route('user.edit', ['id' => $id])); + } + + if (param($request, 'password') !== null && !empty(param($request, 'password'))) { + $this->database->query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=? 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->id, + ]); + } else { + $this->database->query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [ + param($request, 'email'), + param($request, 'username'), + param($request, 'is_admin') !== null ? 1 : 0, + param($request, 'is_active') !== null ? 1 : 0, + $user->id, + ]); + } + + $this->session->alert(lang('user_updated', [param($request, 'username')]), 'success'); + $this->logger->info('User '.$this->session->get('username')." updated $user->id.", [ + array_diff_key((array)$user, array_flip(['password'])), + array_diff_key($request->getParsedBody(), array_flip(['password'])), + ]); + + return redirect($response, route('user.index')); + + } + + /** + * @param Request $request + * @param Response $response + * @param int $id + * @return Response + * @throws HttpNotFoundException + */ + public function delete(Request $request, Response $response, int $id): Response + { + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + + if (!$user) { + throw new HttpNotFoundException($request, $response); + } + + if ($user->id === $this->session->get('user_id')) { + $this->session->alert(lang('cannot_delete'), 'danger'); + return redirect($response, route('user.index')); + } + + $this->database->query('DELETE FROM `users` WHERE `id` = ?', $user->id); + + $this->session->alert(lang('user_deleted'), 'success'); + $this->logger->info('User '.$this->session->get('username')." deleted $user->id."); + + return redirect($response, route('user.index')); + } + + /** + * @param Request $request + * @param Response $response + * @return Response + * @throws HttpNotFoundException + * @throws HttpUnauthorizedException + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function profile(Request $request, Response $response): Response + { + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $this->session->get('user_id'))->fetch(); + + if (!$user) { + throw new HttpNotFoundException($request); + } + + if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) { + throw new HttpUnauthorizedException($request); + } + + return view()->render($response, 'user/edit.twig', [ + 'profile' => true, + 'user' => $user, + ]); + } + + /** + * @param Request $request + * @param Response $response + * @param int $id + * @return Response + * @throws HttpNotFoundException + * @throws HttpUnauthorizedException + */ + public function profileEdit(Request $request, Response $response, int $id): Response + { + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + + if (!$user) { + throw new HttpNotFoundException($request, $response); + } + + if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) { + throw new HttpUnauthorizedException($request); + } + + if (param($request, 'email') === null) { + $this->session->alert(lang('email_required'), 'danger'); + return redirect($response, route('profile')); + } + + 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('profile')); + } + + if (param($request, 'password') !== null && !empty(param($request, 'password'))) { + $this->database->query('UPDATE `users` SET `email`=?, `password`=? WHERE `id` = ?', [ + param($request, 'email'), + password_hash(param($request, 'password'), PASSWORD_DEFAULT), + $user->id, + ]); + } else { + $this->database->query('UPDATE `users` SET `email`=? WHERE `id` = ?', [ + param($request, 'email'), + $user->id, + ]); + } + + $this->session->alert(lang('profile_updated'), 'success'); + $this->logger->info('User '.$this->session->get('username')." updated profile of $user->id."); + + return redirect($response, route('profile')); + } + + /** + * @param Request $request + * @param Response $response + * @param int $id + * @return Response + * @throws HttpNotFoundException + * @throws HttpUnauthorizedException + */ + public function refreshToken(Request $request, Response $response, int $id): Response + { + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + + if (!$user) { + throw new HttpNotFoundException($request, $response); + } + + if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) { + throw new HttpUnauthorizedException($request); + } + + $token = $this->generateNewToken(); + + $this->database->query('UPDATE `users` SET `token`=? WHERE `id` = ?', [ + $token, + $user->id, + ]); + + $this->logger->info('User '.$this->session->get('username')." refreshed token of user $user->id."); + + $response->getBody()->write($token); + + return $response; + } + + /** + * @param Request $request + * @param Response $response + * @param int $id + * @return Response + * @throws HttpNotFoundException + * @throws HttpUnauthorizedException + */ + public function getShareXconfigFile(Request $request, Response $response, int $id): Response + { + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + + if (!$user) { + throw new HttpNotFoundException($request, $response); + } + + if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) { + throw new HttpUnauthorizedException($request); + } + + if ($user->token === null || $user->token === '') { + $this->session->alert('You don\'t have a personal upload token. (Click the update token button and try again)', 'danger'); + return redirect($response, $request->getHeaderLine('HTTP_REFERER')); + } + + $json = [ + 'DestinationType' => 'ImageUploader, TextUploader, FileUploader', + 'RequestURL' => route('upload'), + 'FileFormName' => 'upload', + 'Arguments' => [ + 'file' => '$filename$', + 'text' => '$input$', + 'token' => $user->token, + ], + 'URL' => '$json:url$', + 'ThumbnailURL' => '$json:url$/raw', + 'DeletionURL' => '$json:url$/delete/'.$user->token, + ]; + + return json($response, $json, 200, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) + ->withHeader('Content-Disposition', 'attachment;filename="'.$user->username.'-ShareX.sxcu"'); + } + + /** + * @param Request $request + * @param Response $response + * @param int $id + * @return Response + * @throws HttpNotFoundException + * @throws HttpUnauthorizedException + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function getUploaderScriptFile(Request $request, Response $response, int $id): Response + { + $user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch(); + + if (!$user) { + throw new HttpNotFoundException($request, $response); + } + + if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) { + throw new HttpUnauthorizedException($request); + } + + if ($user->token === null || $user->token === '') { + $this->session->alert('You don\'t have a personal upload token. (Click the update token button and try again)', 'danger'); + return redirect($response, $request->getHeaderLine('HTTP_REFERER')); + } + + return view()->render($response->withHeader('Content-Disposition', 'attachment;filename="xbackbone_uploader_'.$user->username.'.sh"'), + 'scripts/xbackbone_uploader.sh.twig', + [ + 'username' => $user->username, + 'upload_url' => route('upload'), + 'token' => $user->token, + ] + ); + } + + /** + * @return string + */ + protected function generateNewToken(): 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; + } } \ No newline at end of file diff --git a/app/Exceptions/Handlers/AppErrorHandler.php b/app/Exceptions/Handlers/AppErrorHandler.php new file mode 100644 index 0000000..8018d18 --- /dev/null +++ b/app/Exceptions/Handlers/AppErrorHandler.php @@ -0,0 +1,14 @@ +critical($error); + } +} \ No newline at end of file diff --git a/app/Exceptions/Handlers/Renderers/HtmlErrorRenderer.php b/app/Exceptions/Handlers/Renderers/HtmlErrorRenderer.php new file mode 100644 index 0000000..93e0e50 --- /dev/null +++ b/app/Exceptions/Handlers/Renderers/HtmlErrorRenderer.php @@ -0,0 +1,45 @@ +string( 'errors/maintenance.twig'); + } + + if ($exception instanceof HttpUnauthorizedException || $exception instanceof HttpForbiddenException) { + return view()->string( 'errors/403.twig'); + } + + if ($exception instanceof HttpMethodNotAllowedException) { + return view()->string( 'errors/405.twig'); + } + + if ($exception instanceof HttpNotFoundException) { + return view()->string( 'errors/404.twig'); + } + + return view()->string('errors/500.twig', ['exception' => $displayErrorDetails ? $exception : null]); + } +} \ No newline at end of file diff --git a/app/Exceptions/MaintenanceException.php b/app/Exceptions/MaintenanceException.php deleted file mode 100644 index 7c4085e..0000000 --- a/app/Exceptions/MaintenanceException.php +++ /dev/null @@ -1,15 +0,0 @@ -database->query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [$this->session->get('user_id')])->fetch()->is_admin) { $this->session->set('admin', false); - throw new UnauthorizedException(); + throw new HttpUnauthorizedException($request); } - return $next($request, $response); + return $handler->handle($request); } } \ No newline at end of file diff --git a/app/Middleware/AuthMiddleware.php b/app/Middleware/AuthMiddleware.php index dc17f0c..39730ab 100644 --- a/app/Middleware/AuthMiddleware.php +++ b/app/Middleware/AuthMiddleware.php @@ -2,33 +2,34 @@ namespace App\Middleware; -use Slim\Http\Request; -use Slim\Http\Response; + +use GuzzleHttp\Psr7\Response; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Server\RequestHandlerInterface as RequestHandler; class AuthMiddleware extends Middleware { - /** - * @param Request $request - * @param Response $response - * @param callable $next - * @return Response - */ - public function __invoke(Request $request, Response $response, callable $next) - { - if (!$this->session->get('logged', false)) { - $this->session->set('redirectTo', (isset($_SERVER['HTTPS']) ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); - return redirect($response, 'login.show'); - } + /** + * @param Request $request + * @param RequestHandler $handler + * @return ResponseInterface + */ + public function __invoke(Request $request, RequestHandler $handler): ResponseInterface + { + if (!$this->session->get('logged', false)) { + $this->session->set('redirectTo', (string)$request->getUri()); + return redirect(new Response(), route('login.show')); + } - if (!$this->database->query('SELECT `id`, `active` FROM `users` WHERE `id` = ? LIMIT 1', [$this->session->get('user_id')])->fetch()->active) { - $this->session->alert('Your account is not active anymore.', 'danger'); - $this->session->set('logged', false); - $this->session->set('redirectTo', (isset($_SERVER['HTTPS']) ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); - return redirect($response, 'login.show'); - } + if (!$this->database->query('SELECT `id`, `active` FROM `users` WHERE `id` = ? LIMIT 1', [$this->session->get('user_id')])->fetch()->active) { + $this->session->alert('Your account is not active anymore.', 'danger'); + $this->session->set('logged', false); + return redirect(new Response(), route('login.show')); + } - return $next($request, $response); - } + return $handler->handle($request); + } } \ No newline at end of file diff --git a/app/Middleware/CheckForMaintenanceMiddleware.php b/app/Middleware/CheckForMaintenanceMiddleware.php index a4280ea..26d21b4 100644 --- a/app/Middleware/CheckForMaintenanceMiddleware.php +++ b/app/Middleware/CheckForMaintenanceMiddleware.php @@ -2,25 +2,26 @@ namespace App\Middleware; -use App\Exceptions\MaintenanceException; -use Slim\Http\Request; -use Slim\Http\Response; +use App\Exceptions\UnderMaintenanceException; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Server\RequestHandlerInterface as RequestHandler; class CheckForMaintenanceMiddleware extends Middleware { - /** - * @param Request $request - * @param Response $response - * @param callable $next - * @return Response - * @throws MaintenanceException - */ - public function __invoke(Request $request, Response $response, callable $next) - { - if (isset($this->settings['maintenance']) && $this->settings['maintenance'] && !$this->database->query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [$this->session->get('user_id')])->fetch()->is_admin) { - throw new MaintenanceException(); - } + /** + * @param Request $request + * @param RequestHandler $handler + * @return Response + * @throws UnderMaintenanceException + */ + public function __invoke(Request $request, RequestHandler $handler): Response + { + if (isset($this->config['maintenance']) && $this->settings['maintenance'] && !$this->database->query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [$this->session->get('user_id')])->fetch()->is_admin) { + throw new UnderMaintenanceException($request); + } - return $next($request, $response); - } + $response = $handler->handle($request); + return $response; + } } \ No newline at end of file diff --git a/app/Middleware/Middleware.php b/app/Middleware/Middleware.php index bf6b24f..c512224 100644 --- a/app/Middleware/Middleware.php +++ b/app/Middleware/Middleware.php @@ -2,37 +2,40 @@ namespace App\Middleware; -use Slim\Container; -use Slim\Http\Request; -use Slim\Http\Response; + +use DI\Container; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Server\RequestHandlerInterface as RequestHandler; abstract class Middleware { - /** @var Container */ - protected $container; + /** @var Container */ + protected $container; - public function __construct(Container $container) - { - $this->container = $container; - } + public function __construct(Container $container) + { + $this->container = $container; + } - /** - * @param $name - * @return mixed|null - * @throws \Interop\Container\Exception\ContainerException - */ - public function __get($name) - { - if ($this->container->has($name)) { - return $this->container->get($name); - } - return null; - } + /** + * @param $name + * @return mixed|null + * @throws \DI\DependencyException + * @throws \DI\NotFoundException + */ + public function __get($name) + { + if ($this->container->has($name)) { + return $this->container->get($name); + } + return null; + } - /** - * @param Request $request - * @param Response $response - * @param callable $next - */ - public abstract function __invoke(Request $request, Response $response, callable $next); + /** + * @param Request $request + * @param RequestHandler $handler + * @return Response + */ + public abstract function __invoke(Request $request, RequestHandler $handler); } \ No newline at end of file diff --git a/app/Web/View.php b/app/Web/View.php new file mode 100644 index 0000000..4386e0b --- /dev/null +++ b/app/Web/View.php @@ -0,0 +1,100 @@ +container = $container; + + $config = $container->get('config'); + $loader = new FilesystemLoader(BASE_DIR.'resources/templates'); + + $twig = new Environment($loader, [ + 'cache' => BASE_DIR.'resources/cache/twig', + 'autoescape' => 'html', + 'debug' => $config['debug'], + 'auto_reload' => $config['debug'], + ]); + + $serverRequestCreator = ServerRequestCreatorFactory::create(); + $request = $serverRequestCreator->createServerRequestFromGlobals(); + + $twig->addGlobal('config', $config); + $twig->addGlobal('request', $request); + $twig->addGlobal('alerts', $container->get('session')->getAlert()); + $twig->addGlobal('session', $container->get('session')->all()); + $twig->addGlobal('current_lang', $container->get('lang')->getLang()); + $twig->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION); + + $twig->addFunction(new TwigFunction('route', 'route')); + $twig->addFunction(new TwigFunction('lang', 'lang')); + $twig->addFunction(new TwigFunction('urlFor', 'urlFor')); + $twig->addFunction(new TwigFunction('asset', 'asset')); + $twig->addFunction(new TwigFunction('mime2font', 'mime2font')); + $twig->addFunction(new TwigFunction('queryParams', 'queryParams')); + $twig->addFunction(new TwigFunction('isDisplayableImage', 'isDisplayableImage')); + + $this->twig = $twig; + } + + /** + * @param Response $response + * @param string $view + * @param array|null $parameters + * @return Response + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError + */ + public function render(Response $response, string $view, ?array $parameters = []) + { + $body = $this->twig->render($view, $parameters); + $response->getBody()->write($body); + return $response; + } + + /** + * @param string $view + * @param array|null $parameters + * @return string + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError + */ + public function string(string $view, ?array $parameters = []) + { + return $this->twig->render($view, $parameters); + } + +} \ No newline at end of file diff --git a/app/helpers.php b/app/helpers.php index b67699b..045cadc 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,318 +1,379 @@ 0.9; $i++, $size /= 1024) { - } - return round($size, $precision) . ' ' . ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i]; - } + /** + * Generate a human readable file size + * @param $size + * @param int $precision + * @return string + */ + function humanFileSize($size, $precision = 2): string + { + for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) { + } + return round($size, $precision).' '.['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i]; + } } if (!function_exists('humanRandomString')) { - /** - * @param int $length - * @return string - */ - function humanRandomString(int $length = 13): string - { - $result = ''; - $numberOffset = round($length * 0.2); - for ($x = 0; $x < $length - $numberOffset; $x++) { - $result .= ($x % 2) ? HUMAN_RANDOM_CHARS[rand(42, 51)] : HUMAN_RANDOM_CHARS[rand(0, 41)]; - } - for ($x = 0; $x < $numberOffset; $x++) { - $result .= rand(0, 9); - } - return $result; - } + /** + * @param int $length + * @return string + */ + function humanRandomString(int $length = 13): string + { + $result = ''; + $numberOffset = round($length * 0.2); + for ($x = 0; $x < $length - $numberOffset; $x++) { + $result .= ($x % 2) ? HUMAN_RANDOM_CHARS[rand(42, 51)] : HUMAN_RANDOM_CHARS[rand(0, 41)]; + } + for ($x = 0; $x < $numberOffset; $x++) { + $result .= rand(0, 9); + } + return $result; + } } if (!function_exists('isDisplayableImage')) { - /** - * @param string $mime - * @return bool - */ - function isDisplayableImage(string $mime): bool - { - return in_array($mime, [ - 'image/apng', - 'image/bmp', - 'image/gif', - 'image/x-icon', - 'image/jpeg', - 'image/png', - 'image/svg', - 'image/svg+xml', - 'image/tiff', - 'image/webp', - ]); - } + /** + * @param string $mime + * @return bool + */ + function isDisplayableImage(string $mime): bool + { + return in_array($mime, [ + 'image/apng', + 'image/bmp', + 'image/gif', + 'image/x-icon', + 'image/jpeg', + 'image/png', + 'image/svg', + 'image/svg+xml', + 'image/tiff', + 'image/webp', + ]); + } } if (!function_exists('stringToBytes')) { - /** - * @param $str - * @return float - */ - function stringToBytes(string $str): float - { - $val = trim($str); - if (is_numeric($val)) { - return (float)$val; - } + /** + * @param $str + * @return float + */ + function stringToBytes(string $str): float + { + $val = trim($str); + if (is_numeric($val)) { + return (float)$val; + } - $last = strtolower($val[strlen($val) - 1]); - $val = substr($val, 0, -1); + $last = strtolower($val[strlen($val) - 1]); + $val = substr($val, 0, -1); - $val = (float)$val; - switch ($last) { - case 'g': - $val *= 1024; - case 'm': - $val *= 1024; - case 'k': - $val *= 1024; - } - return $val; - } + $val = (float)$val; + switch ($last) { + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + } + return $val; + } } if (!function_exists('removeDirectory')) { - /** - * Remove a directory and it's content - * @param $path - */ - function removeDirectory($path) - { - $files = glob($path . '/*'); - foreach ($files as $file) { - is_dir($file) ? removeDirectory($file) : unlink($file); - } - rmdir($path); - return; - } + /** + * Remove a directory and it's content + * @param $path + */ + function removeDirectory($path) + { + $files = glob($path.'/*'); + foreach ($files as $file) { + is_dir($file) ? removeDirectory($file) : unlink($file); + } + rmdir($path); + return; + } } if (!function_exists('cleanDirectory')) { - /** - * Removes all directory contents - * @param $path - */ - function cleanDirectory($path) - { - $directoryIterator = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS); - $iteratorIterator = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST); - foreach ($iteratorIterator as $file) { - if ($file->getFilename() !== '.gitkeep') { - $file->isDir() ? rmdir($file) : unlink($file); - } - } - } + /** + * Removes all directory contents + * @param $path + */ + function cleanDirectory($path) + { + $directoryIterator = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS); + $iteratorIterator = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST); + foreach ($iteratorIterator as $file) { + if ($file->getFilename() !== '.gitkeep') { + $file->isDir() ? rmdir($file) : unlink($file); + } + } + } } -if (!function_exists('redirect')) { - /** - * Set the redirect response - * @param \Slim\Http\Response $response - * @param string $path - * @param array $args - * @param null $status - * @return \Slim\Http\Response - */ - function redirect(\Slim\Http\Response $response, string $path, $args = [], $status = null) - { - if (substr($path, 0, 1) === '/' || substr($path, 0, 3) === '../' || substr($path, 0, 2) === './') { - $url = urlFor($path); - } else { - $url = route($path, $args); - } +if (!function_exists('resolve')) { + /** + * Resolve a service from de DI container + * @param string $service + * @return mixed + */ + function resolve(string $service) + { + global $app; + return $app->getContainer()->get($service); + } +} - return $response->withRedirect($url, $status); - } +if (!function_exists('view')) { + /** + * Render a view to the response body + * @return \App\Web\View + */ + function view() + { + return resolve('view'); + } +} + + +if (!function_exists('redirect')) { + /** + * Set the redirect response + * @param Response $response + * @param string $url + * @param int $status + * @return Response + */ + function redirect(Response $response, string $url, $status = 302) + { + return $response + ->withHeader('Location', $url) + ->withStatus($status); + } } if (!function_exists('asset')) { - /** - * Get the asset link with timestamp - * @param string $path - * @return string - */ - function asset(string $path): string - { - return urlFor($path, '?' . filemtime(realpath(BASE_DIR . $path))); - } + /** + * Get the asset link with timestamp + * @param string $path + * @return string + */ + function asset(string $path): string + { + return urlFor($path, '?'.filemtime(realpath(BASE_DIR.$path))); + } } if (!function_exists('urlFor')) { - /** - * Generate the app url given a path - * @param string $path - * @param string $append - * @return string - */ - function urlFor(string $path, string $append = ''): string - { - global $app; - $baseUrl = $app->getContainer()->get('settings')['base_url']; - return $baseUrl . $path . $append; - } + /** + * Generate the app url given a path + * @param string $path + * @param string $append + * @return string + */ + function urlFor(string $path, string $append = ''): string + { + $baseUrl = resolve('config')['base_url']; + return $baseUrl.$path.$append; + } } if (!function_exists('route')) { - /** - * Generate the app url given a path - * @param string $path - * @param array $args - * @param string $append - * @return string - */ - function route(string $path, array $args = [], string $append = ''): string - { - global $app; - $uri = $app->getContainer()->get('router')->relativePathFor($path, $args); - return urlFor($uri, $append); - } + /** + * Generate the app url given a path + * @param string $path + * @param array $args + * @param string $append + * @return string + */ + function route(string $path, array $args = [], string $append = ''): string + { + global $app; + $uri = $app->getRouteCollector()->getRouteParser()->relativeUrlFor($path, $args); + return urlFor($uri, $append); + } +} + +if (!function_exists('param')) { + /** + * Get a parameter from the request + * @param Request $request + * @param string $name + * @param null $default + * @return string + */ + function param(Request $request, string $name, $default = null) + { + if ($request->getMethod() === 'GET') { + $params = $request->getQueryParams(); + } else { + $params = $request->getParsedBody(); + } + + if (isset($params[$name])) { + return $params[$name]; + } + return $default; + } +} + +if (!function_exists('json')) { + /** + * Return a json response + * @param Response $response + * @param $data + * @param int $status + * @param int $options + * @return Response + */ + function json(Response $response, $data, int $status = 200, $options = 0): Response + { + $response->getBody()->write(json_encode($data, $options)); + return $response + ->withStatus($status) + ->withHeader('Content-Type', 'application/json'); + } } if (!function_exists('lang')) { - /** - * @param string $key - * @param array $args - * @return string - */ - function lang(string $key, $args = []): string - { - global $app; - return $app->getContainer()->get('lang')->get($key, $args); - } + /** + * @param string $key + * @param array $args + * @return string + */ + function lang(string $key, $args = []): string + { + return resolve('lang')->get($key, $args); + } } if (!function_exists('isBot')) { - /** - * @param string $userAgent - * @return boolean - */ - function isBot(string $userAgent) - { - $bots = [ - 'TelegramBot', - 'facebookexternalhit/', - 'Discordbot/', - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0', // The discord service bot? - 'Facebot', - 'curl/', - 'wget/', - ]; + /** + * @param string $userAgent + * @return boolean + */ + function isBot(string $userAgent) + { + $bots = [ + 'TelegramBot', + 'facebookexternalhit/', + 'Discordbot/', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0', // The discord service bot? + 'Facebot', + 'curl/', + 'wget/', + ]; - foreach ($bots as $bot) { - if (stripos($userAgent, $bot) !== false) { - return true; - } - } + foreach ($bots as $bot) { + if (stripos($userAgent, $bot) !== false) { + return true; + } + } - return false; - } + return false; + } } if (!function_exists('mime2font')) { - /** - * Convert get the icon from the file mimetype - * @param $mime - * @return mixed|string - */ - function mime2font($mime) - { - $classes = [ - 'image' => 'fa-file-image', - 'audio' => 'fa-file-audio', - 'video' => 'fa-file-video', - 'application/pdf' => 'fa-file-pdf', - 'application/msword' => 'fa-file-word', - 'application/vnd.ms-word' => 'fa-file-word', - 'application/vnd.oasis.opendocument.text' => 'fa-file-word', - 'application/vnd.openxmlformats-officedocument.wordprocessingml' => 'fa-file-word', - 'application/vnd.ms-excel' => 'fa-file-excel', - 'application/vnd.openxmlformats-officedocument.spreadsheetml' => 'fa-file-excel', - 'application/vnd.oasis.opendocument.spreadsheet' => 'fa-file-excel', - 'application/vnd.ms-powerpoint' => 'fa-file-powerpoint', - 'application/vnd.openxmlformats-officedocument.presentationml' => 'fa-file-powerpoint', - 'application/vnd.oasis.opendocument.presentation' => 'fa-file-powerpoint', - 'text/plain' => 'fa-file-alt', - 'text/html' => 'fa-file-code', - 'text/x-php' => 'fa-file-code', - 'application/json' => 'fa-file-code', - 'application/gzip' => 'fa-file-archive', - 'application/zip' => 'fa-file-archive', - 'application/octet-stream' => 'fa-file-alt', - ]; + /** + * Convert get the icon from the file mimetype + * @param $mime + * @return mixed|string + */ + function mime2font($mime) + { + $classes = [ + 'image' => 'fa-file-image', + 'audio' => 'fa-file-audio', + 'video' => 'fa-file-video', + 'application/pdf' => 'fa-file-pdf', + 'application/msword' => 'fa-file-word', + 'application/vnd.ms-word' => 'fa-file-word', + 'application/vnd.oasis.opendocument.text' => 'fa-file-word', + 'application/vnd.openxmlformats-officedocument.wordprocessingml' => 'fa-file-word', + 'application/vnd.ms-excel' => 'fa-file-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml' => 'fa-file-excel', + 'application/vnd.oasis.opendocument.spreadsheet' => 'fa-file-excel', + 'application/vnd.ms-powerpoint' => 'fa-file-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml' => 'fa-file-powerpoint', + 'application/vnd.oasis.opendocument.presentation' => 'fa-file-powerpoint', + 'text/plain' => 'fa-file-alt', + 'text/html' => 'fa-file-code', + 'text/x-php' => 'fa-file-code', + 'application/json' => 'fa-file-code', + 'application/gzip' => 'fa-file-archive', + 'application/zip' => 'fa-file-archive', + 'application/octet-stream' => 'fa-file-alt', + ]; - foreach ($classes as $fullMime => $class) { - if (strpos($mime, $fullMime) === 0) { - return $class; - } - } - return 'fa-file'; - } + foreach ($classes as $fullMime => $class) { + if (strpos($mime, $fullMime) === 0) { + return $class; + } + } + return 'fa-file'; + } } if (!function_exists('dd')) { - /** - * Dumps all the given vars and halt the execution. - */ - function dd() - { - array_map(function ($x) { - echo '
'; - print_r($x); - echo ''; - }, func_get_args()); - die(); - } + /** + * Dumps all the given vars and halt the execution. + */ + function dd() + { + array_map(function ($x) { + echo '
'; + print_r($x); + echo ''; + }, func_get_args()); + die(); + } } if (!function_exists('queryParams')) { - /** - * Get the query parameters of the current request. - * @param array $replace - * @return string - * @throws \Interop\Container\Exception\ContainerException - */ - function queryParams(array $replace = []) - { - global $container; - /** @var \Slim\Http\Request $request */ - $request = $container->get('request'); + /** + * Get the query parameters of the current request. + * @param array $replace + * @return string + */ + function queryParams(array $replace = []) + { + $serverRequestCreator = ServerRequestCreatorFactory::create(); + $request = $serverRequestCreator->createServerRequestFromGlobals(); - $params = array_replace_recursive($request->getQueryParams(), $replace); + $params = array_replace_recursive($request->getQueryParams(), $replace); - return !empty($params) ? '?' . http_build_query($params) : ''; - } + return !empty($params) ? '?'.http_build_query($params) : ''; + } } if (!function_exists('glob_recursive')) { - /** - * Does not support flag GLOB_BRACE - * @param $pattern - * @param int $flags - * @return array|false - */ - function glob_recursive($pattern, $flags = 0) - { - $files = glob($pattern, $flags); - foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { - $files = array_merge($files, glob_recursive($dir . '/' . basename($pattern), $flags)); - } - return $files; - } + /** + * Does not support flag GLOB_BRACE + * @param $pattern + * @param int $flags + * @return array|false + */ + function glob_recursive($pattern, $flags = 0) + { + $files = glob($pattern, $flags); + foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { + $files = array_merge($files, glob_recursive($dir.'/'.basename($pattern), $flags)); + } + return $files; + } } \ No newline at end of file diff --git a/app/routes.php b/app/routes.php index bd83bf0..dc0a8d2 100644 --- a/app/routes.php +++ b/app/routes.php @@ -10,59 +10,61 @@ use App\Controllers\UserController; use App\Middleware\AdminMiddleware; use App\Middleware\AuthMiddleware; use App\Middleware\CheckForMaintenanceMiddleware; +use Slim\Routing\RouteCollectorProxy; -$app->group('', function () { - $this->get('/home[/page/{page}]', DashboardController::class . ':home')->setName('home'); +global $app; +$app->group('', function (RouteCollectorProxy $group) { + $group->get('/home[/page/{page}]', [DashboardController::class, 'home'])->setName('home'); - $this->group('', function () { - $this->get('/home/switchView', DashboardController::class . ':switchView')->setName('switchView'); + $group->group('', function (RouteCollectorProxy $group) { + $group->get('/home/switchView', [DashboardController::class, 'switchView'])->setName('switchView'); - $this->get('/system/deleteOrphanFiles', AdminController::class . ':deleteOrphanFiles')->setName('system.deleteOrphanFiles'); + $group->get('/system/deleteOrphanFiles', [AdminController::class, 'deleteOrphanFiles'])->setName('system.deleteOrphanFiles'); - $this->get('/system/themes', ThemeController::class . ':getThemes')->setName('theme'); - $this->post('/system/theme/apply', ThemeController::class . ':applyTheme')->setName('theme.apply'); + $group->get('/system/themes', [ThemeController::class, 'getThemes'])->setName('theme'); + $group->post('/system/theme/apply', [ThemeController::class, 'applyTheme'])->setName('theme.apply'); - $this->post('/system/lang/apply', AdminController::class . ':applyLang')->setName('lang.apply'); + $group->post('/system/lang/apply', [AdminController::class, 'applyLang'])->setName('lang.apply'); - $this->post('/system/upgrade', UpgradeController::class . ':upgrade')->setName('system.upgrade'); - $this->get('/system/checkForUpdates', UpgradeController::class . ':checkForUpdates')->setName('system.checkForUpdates'); + $group->post('/system/upgrade', [UpgradeController::class, 'upgrade'])->setName('system.upgrade'); + $group->get('/system/checkForUpdates', [UpgradeController::class, 'checkForUpdates'])->setName('system.checkForUpdates'); - $this->get('/system', AdminController::class . ':system')->setName('system'); + $group->get('/system', [AdminController::class, 'system'])->setName('system'); - $this->get('/users[/page/{page}]', UserController::class . ':index')->setName('user.index'); - })->add(AdminMiddleware::class); + $group->get('/users[/page/{page}]', [UserController::class, 'index'])->setName('user.index'); + })->add(AdminMiddleware::class); - $this->group('/user', function () { + $group->group('/user', function (RouteCollectorProxy $group) { - $this->get('/create', UserController::class . ':create')->setName('user.create'); - $this->post('/create', UserController::class . ':store')->setName('user.store'); - $this->get('/{id}/edit', UserController::class . ':edit')->setName('user.edit'); - $this->post('/{id}', UserController::class . ':update')->setName('user.update'); - $this->get('/{id}/delete', UserController::class . ':delete')->setName('user.delete'); - })->add(AdminMiddleware::class); + $group->get('/create', [UserController::class, 'create'])->setName('user.create'); + $group->post('/create', [UserController::class, 'store'])->setName('user.store'); + $group->get('/{id}/edit', [UserController::class, 'edit'])->setName('user.edit'); + $group->post('/{id}', [UserController::class, 'update'])->setName('user.update'); + $group->get('/{id}/delete', [UserController::class, 'delete'])->setName('user.delete'); + })->add(AdminMiddleware::class); - $this->get('/profile', UserController::class . ':profile')->setName('profile'); - $this->post('/profile/{id}', UserController::class . ':profileEdit')->setName('profile.update'); - $this->post('/user/{id}/refreshToken', UserController::class . ':refreshToken')->setName('refreshToken'); - $this->get('/user/{id}/config/sharex', UserController::class . ':getShareXconfigFile')->setName('config.sharex'); - $this->get('/user/{id}/config/script', UserController::class . ':getUploaderScriptFile')->setName('config.script'); + $group->get('/profile', [UserController::class, 'profile'])->setName('profile'); + $group->post('/profile/{id}', [UserController::class, 'profileEdit'])->setName('profile.update'); + $group->post('/user/{id}/refreshToken', [UserController::class, 'refreshToken'])->setName('refreshToken'); + $group->get('/user/{id}/config/sharex', [UserController::class, 'getShareXconfigFile'])->setName('config.sharex'); + $group->get('/user/{id}/config/script', [UserController::class, 'getUploaderScriptFile'])->setName('config.script'); - $this->post('/upload/{id}/publish', UploadController::class . ':togglePublish')->setName('upload.publish'); - $this->post('/upload/{id}/unpublish', UploadController::class . ':togglePublish')->setName('upload.unpublish'); - $this->get('/upload/{id}/raw', UploadController::class . ':getRawById')->add(AdminMiddleware::class)->setName('upload.raw'); - $this->post('/upload/{id}/delete', UploadController::class . ':delete')->setName('upload.delete'); + $group->post('/upload/{id}/publish', [UploadController::class, 'togglePublish'])->setName('upload.publish'); + $group->post('/upload/{id}/unpublish', [UploadController::class, 'togglePublish'])->setName('upload.unpublish'); + $group->get('/upload/{id}/raw', [UploadController::class, 'getRawById'])->add(AdminMiddleware::class)->setName('upload.raw'); + $group->post('/upload/{id}/delete', [UploadController::class, 'delete'])->setName('upload.delete'); })->add(App\Middleware\CheckForMaintenanceMiddleware::class)->add(AuthMiddleware::class); -$app->get('/', DashboardController::class . ':redirects')->setName('root'); -$app->get('/login', LoginController::class . ':show')->setName('login.show'); -$app->post('/login', LoginController::class . ':login')->setName('login'); -$app->map(['GET', 'POST'], '/logout', LoginController::class . ':logout')->setName('logout'); +$app->get('/', [DashboardController::class, 'redirects'])->setName('root'); +$app->get('/login', [LoginController::class, 'show'])->setName('login.show'); +$app->post('/login', [LoginController::class, 'login'])->setName('login'); +$app->map(['GET', 'POST'], '/logout', [LoginController::class, 'logout'])->setName('logout'); -$app->post('/upload', UploadController::class . ':upload')->setName('upload'); +$app->post('/upload', [UploadController::class, 'upload'])->setName('upload'); -$app->get('/{userCode}/{mediaCode}', UploadController::class . ':show')->setName('public'); -$app->get('/{userCode}/{mediaCode}/delete/{token}', UploadController::class . ':show')->setName('public.delete.show')->add(CheckForMaintenanceMiddleware::class); -$app->post('/{userCode}/{mediaCode}/delete/{token}', UploadController::class . ':deleteByToken')->setName('public.delete')->add(CheckForMaintenanceMiddleware::class); -$app->get('/{userCode}/{mediaCode}/raw', UploadController::class . ':showRaw')->setName('public.raw')->setOutputBuffering(false); -$app->get('/{userCode}/{mediaCode}/download', UploadController::class . ':download')->setName('public.download')->setOutputBuffering(false); \ No newline at end of file +$app->get('/{userCode}/{mediaCode}', [UploadController::class, 'show'])->setName('public'); +$app->get('/{userCode}/{mediaCode}/delete/{token}', [UploadController::class, 'show'])->setName('public.delete.show')->add(CheckForMaintenanceMiddleware::class); +$app->post('/{userCode}/{mediaCode}/delete/{token}', [UploadController::class, 'deleteByToken'])->setName('public.delete')->add(CheckForMaintenanceMiddleware::class); +$app->get('/{userCode}/{mediaCode}/raw', [UploadController::class, 'showRaw'])->setName('public.raw'); +$app->get('/{userCode}/{mediaCode}/download', [UploadController::class, 'download'])->setName('public.download'); \ No newline at end of file diff --git a/bootstrap/app.php b/bootstrap/app.php index 752cd9c..4da2958 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,12 +1,18 @@ 'XBackBone', - 'base_url' => isset($_SERVER['HTTPS']) ? 'https://' . $_SERVER['HTTP_HOST'] : 'http://' . $_SERVER['HTTP_HOST'], - 'displayErrorDetails' => false, - 'maintenance' => false, - 'db' => [ - 'connection' => 'sqlite', - 'dsn' => BASE_DIR . 'resources/database/xbackbone.db', - 'username' => null, - 'password' => null, - ], - 'storage' => [ - 'driver' => 'local', - 'path' => realpath(__DIR__ . '/') . DIRECTORY_SEPARATOR . 'storage', - ], -], require BASE_DIR . 'config.php'); + 'app_name' => 'XBackBone', + 'base_url' => isset($_SERVER['HTTPS']) ? 'https://'.$_SERVER['HTTP_HOST'] : 'http://'.$_SERVER['HTTP_HOST'], + 'debug' => false, + 'maintenance' => false, + 'db' => [ + 'connection' => 'sqlite', + 'dsn' => BASE_DIR.'resources/database/xbackbone.db', + 'username' => null, + 'password' => null, + ], + 'storage' => [ + 'driver' => 'local', + 'path' => realpath(__DIR__.'/').DIRECTORY_SEPARATOR.'storage', + ], +], require BASE_DIR.'config.php'); -if (!$config['displayErrorDetails']) { - $config['routerCacheFile'] = BASE_DIR . 'resources/cache/routes.cache.php'; +$builder = new ContainerBuilder(); + +if (!$config['debug']) { + $builder->enableCompilation(BASE_DIR.'/resources/cache/di/'); + $builder->writeProxiesToFile(true, BASE_DIR.'/resources/cache/proxies'); +} +$builder->addDefinitions([ + 'config' => value($config), + + 'logger' => factory(function (Container $container) { + $logger = new Logger('app'); + + $streamHandler = new RotatingFileHandler(BASE_DIR.'logs/log.txt', 10, Logger::DEBUG); + + $lineFormatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", "Y-m-d H:i:s"); + $lineFormatter->includeStacktraces(true); + + $streamHandler->setFormatter($lineFormatter); + + $logger->pushHandler($streamHandler); + + return $logger; + }), + + 'session' => factory(function (Container $container) { + return new Session('xbackbone_session', BASE_DIR.'resources/sessions'); + }), + + 'database' => factory(function (Container $container) { + $config = $container->get('config'); + $dsn = $config['db']['connection'] === 'sqlite' ? BASE_DIR.$config['db']['dsn'] : $config['db']['dsn']; + return new DB($config['db']['connection'].':'.$dsn, $config['db']['username'], $config['db']['password']); + }), + + 'storage' => factory(function (Container $container) { + $config = $container->get('config'); + switch ($config['storage']['driver']) { + case 'local': + return new Filesystem(new Local($config['storage']['path'])); + case 's3': + $client = new S3Client([ + 'credentials' => [ + 'key' => $config['storage']['key'], + 'secret' => $config['storage']['secret'], + ], + 'region' => $config['storage']['region'], + 'version' => 'latest', + ]); + + return new Filesystem(new AwsS3Adapter($client, $config['storage']['bucket'], $config['storage']['path'])); + case 'dropbox': + $client = new DropboxClient($config['storage']['token']); + return new Filesystem(new DropboxAdapter($client), ['case_sensitive' => false]); + case 'ftp': + return new Filesystem(new FtpAdapter([ + 'host' => $config['storage']['host'], + 'username' => $config['storage']['username'], + 'password' => $config['storage']['password'], + 'port' => $config['storage']['port'], + 'root' => $config['storage']['path'], + 'passive' => $config['storage']['passive'], + 'ssl' => $config['storage']['ssl'], + 'timeout' => 30, + ])); + case 'google-cloud': + $client = new StorageClient([ + 'projectId' => $config['storage']['project_id'], + 'keyFilePath' => $config['storage']['key_path'], + ]); + return new Filesystem(new GoogleStorageAdapter($client, $client->bucket($config['storage']['bucket']))); + default: + throw new InvalidArgumentException('The driver specified is not supported.'); + } + }), + + 'lang' => factory(function (Container $container) { + $config = $container->get('config'); + if (isset($config['lang'])) { + return Lang::build($config['lang'], BASE_DIR.'resources/lang/'); + } + return Lang::build(Lang::recognize(), BASE_DIR.'resources/lang/'); + }), + + 'view' => factory(function ($container) { + return new View($container); + }), +]); + +$app = Bridge::create($builder->build()); + +if (!$config['debug']) { + $app->getRouteCollector()->setCacheFile(BASE_DIR.'resources/cache/routes.cache.php'); } -$container = new Container(['settings' => $config]); - -$container['config'] = function ($container) use ($config) { - return $config; -}; - -$container['logger'] = function ($container) { - $logger = new Logger('app'); - - $streamHandler = new RotatingFileHandler(BASE_DIR . 'logs/log.txt', 10, Logger::DEBUG); - - $lineFormatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", "Y-m-d H:i:s"); - $lineFormatter->includeStacktraces(true); - - $streamHandler->setFormatter($lineFormatter); - - $logger->pushHandler($streamHandler); - - return $logger; -}; - -$container['session'] = function ($container) { - return new Session('xbackbone_session', BASE_DIR . 'resources/sessions'); -}; - -$container['database'] = function ($container) use (&$config) { - $dsn = $config['db']['connection'] === 'sqlite' ? BASE_DIR . $config['db']['dsn'] : $config['db']['dsn']; - return new DB($config['db']['connection'] . ':' . $dsn, $config['db']['username'], $config['db']['password']); -}; - -$container['storage'] = function ($container) use (&$config) { - - switch ($config['storage']['driver']) { - case 'local': - return new Filesystem(new Local($config['storage']['path'])); - case 's3': - $client = new S3Client([ - 'credentials' => [ - 'key' => $config['storage']['key'], - 'secret' => $config['storage']['secret'], - ], - 'region' => $config['storage']['region'], - 'version' => 'latest', - ]); - - return new Filesystem(new AwsS3Adapter($client, $config['storage']['bucket'], $config['storage']['path'])); - case 'dropbox': - $client = new DropboxClient($config['storage']['token']); - return new Filesystem(new DropboxAdapter($client), ['case_sensitive' => false]); - case 'ftp': - return new Filesystem(new FtpAdapter([ - 'host' => $config['storage']['host'], - 'username' => $config['storage']['username'], - 'password' => $config['storage']['password'], - 'port' => $config['storage']['port'], - 'root' => $config['storage']['path'], - 'passive' => $config['storage']['passive'], - 'ssl' => $config['storage']['ssl'], - 'timeout' => 30, - ])); - case 'google-cloud': - $client = new StorageClient([ - 'projectId' => $config['storage']['project_id'], - 'keyFilePath' => $config['storage']['key_path'], - ]); - return new Filesystem(new GoogleStorageAdapter($client, $client->bucket($config['storage']['bucket']))); - default: - throw new InvalidArgumentException('The driver specified is not supported.'); - } -}; - -$container['lang'] = function ($container) use (&$config) { - if (isset($config['lang'])) { - return Lang::build($config['lang'], BASE_DIR . 'resources/lang/'); - } - return Lang::build(Lang::recognize(), BASE_DIR . 'resources/lang/'); -}; - -$container['view'] = function ($container) use (&$config) { - $view = new Twig(BASE_DIR . 'resources/templates', [ - 'cache' => BASE_DIR . 'resources/cache', - 'autoescape' => 'html', - 'debug' => $config['displayErrorDetails'], - 'auto_reload' => $config['displayErrorDetails'], - ]); - - // Instantiate and add Slim specific extension - $router = $container->get('router'); - $uri = Uri::createFromEnvironment(new Environment($_SERVER)); - $view->addExtension(new Slim\Views\TwigExtension($router, $uri)); - - $view->getEnvironment()->addGlobal('config', $config); - $view->getEnvironment()->addGlobal('request', $container->get('request')); - $view->getEnvironment()->addGlobal('alerts', $container->get('session')->getAlert()); - $view->getEnvironment()->addGlobal('session', $container->get('session')->all()); - $view->getEnvironment()->addGlobal('current_lang', $container->get('lang')->getLang()); - $view->getEnvironment()->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION); - - $view->getEnvironment()->addFunction(new TwigFunction('route', 'route')); - $view->getEnvironment()->addFunction(new TwigFunction('lang', 'lang')); - $view->getEnvironment()->addFunction(new TwigFunction('urlFor', 'urlFor')); - $view->getEnvironment()->addFunction(new TwigFunction('asset', 'asset')); - $view->getEnvironment()->addFunction(new TwigFunction('mime2font', 'mime2font')); - $view->getEnvironment()->addFunction(new TwigFunction('queryParams', 'queryParams')); - $view->getEnvironment()->addFunction(new TwigFunction('isDisplayableImage', 'isDisplayableImage')); - return $view; -}; - -$container['phpErrorHandler'] = function ($container) { - return function (Request $request, Response $response, Throwable $error) use (&$container) { - $container->logger->critical('Fatal runtime error during app execution', ['exception' => $error]); - return $container->view->render($response->withStatus(500), 'errors/500.twig', ['exception' => $error]); - }; -}; - -$container['errorHandler'] = function ($container) { - return function (Request $request, Response $response, Exception $exception) use (&$container) { - - if ($exception instanceof MaintenanceException) { - return $container->view->render($response->withStatus(503), 'errors/maintenance.twig'); - } - - if ($exception instanceof UnauthorizedException) { - return $container->view->render($response->withStatus(403), 'errors/403.twig'); - } - - $container->logger->critical('Fatal exception during app execution', ['exception' => $exception]); - return $container->view->render($response->withStatus(500), 'errors/500.twig', ['exception' => $exception]); - }; -}; - -$container['notAllowedHandler'] = function ($container) { - return function (Request $request, Response $response, $methods) use (&$container) { - return $container->view->render($response->withStatus(405)->withHeader('Allow', implode(', ', $methods)), 'errors/405.twig'); - }; -}; - -$container['notFoundHandler'] = function ($container) { - return function (Request $request, Response $response) use (&$container) { - $response->withStatus(404)->withHeader('Content-Type', 'text/html'); - return $container->view->render($response, 'errors/404.twig'); - }; -}; - -$app = new App($container); +$app->addRoutingMiddleware(); // Permanently redirect paths with a trailing slash to their non-trailing counterpart -$app->add(function (Request $request, Response $response, callable $next) { - $uri = $request->getUri(); - $path = $uri->getPath(); +$app->add(function (Request $request, RequestHandler $handler) { + $uri = $request->getUri(); + $path = $uri->getPath(); - if ($path !== '/' && substr($path, -1) === '/') { - $uri = $uri->withPath(substr($path, 0, -1)); + if ($path !== '/' && substr($path, -1) === '/') { + // permanently redirect paths with a trailing slash + // to their non-trailing counterpart + $uri = $uri->withPath(substr($path, 0, -1)); - if ($request->getMethod() === 'GET') { - return $response->withRedirect((string)$uri, 301); - } else { - return $next($request->withUri($uri), $response); - } - } + if ($request->getMethod() == 'GET') { + $response = new Response(); + return $response->withStatus(301) + ->withHeader('Location', (string)$uri); + } else { + $request = $request->withUri($uri); + } + } - return $next($request, $response); + return $handler->handle($request); }); // Load the application routes -require BASE_DIR . 'app/routes.php'; +require BASE_DIR.'app/routes.php'; + +// Configure the error handler +$errorHandler = new AppErrorHandler($app->getCallableResolver(), $app->getResponseFactory()); +$errorHandler->registerErrorRenderer('text/html', HtmlErrorRenderer::class); + +// Add Error Middleware +$errorMiddleware = $app->addErrorMiddleware($config['debug'], true, true); +$errorMiddleware->setDefaultErrorHandler($errorHandler); return $app; \ No newline at end of file diff --git a/composer.json b/composer.json index 178ee6d..2a3fdae 100644 --- a/composer.json +++ b/composer.json @@ -1,26 +1,33 @@ { "name": "sergix44/xbackbone", - "version": "2.6.6", + "version": "3.0", "description": "A lightweight ShareX PHP backend", "type": "project", "require": { "php": ">=7.1", - "slim/slim": "^3.0", - "slim/twig-view": "^2.4", + "ext-intl": "*", + "ext-json": "*", + "ext-gd": "*", + "ext-pdo": "*", + "ext-zip": "*", + "slim/slim": "^4.0", + "php-di/slim-bridge": "^3.0", + "twig/twig": "^2.12", + "guzzlehttp/psr7": "^1.6", "league/flysystem": "^1.0.45", "monolog/monolog": "^1.23", "intervention/image": "^2.4", "league/flysystem-aws-s3-v3": "^1.0", "spatie/flysystem-dropbox": "^1.0", "superbalist/flysystem-google-storage": "^7.2", - "ext-intl": "*", - "ext-json": "*", - "ext-gd": "*", - "ext-pdo": "*", - "ext-zip": "*" + "http-interop/http-factory-guzzle": "^1.0" + + }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true }, - "prefer-stable": true, - "minimum-stability": "dev", "autoload": { "files": [ "app/helpers.php" diff --git a/composer.lock b/composer.lock index b558c3f..fbb9358 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "94fb8de56fe9b216b1db862119cb8aa0", + "content-hash": "e7ad26b98d3ded901c5c663673f3bfff", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.112.26", + "version": "3.115.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "eda066813388de5ab8584f06707dacd0b15908b0" + "reference": "15792196be1b3b1b5663bca7b6cd021d005e2265" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eda066813388de5ab8584f06707dacd0b15908b0", - "reference": "eda066813388de5ab8584f06707dacd0b15908b0", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/15792196be1b3b1b5663bca7b6cd021d005e2265", + "reference": "15792196be1b3b1b5663bca7b6cd021d005e2265", "shasum": "" }, "require": { @@ -87,38 +87,7 @@ "s3", "sdk" ], - "time": "2019-10-22T18:17:52+00:00" - }, - { - "name": "container-interop/container-interop", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/container-interop/container-interop.git", - "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" - }, - "dist": { - "type": "zip", - "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" + "time": "2019-11-11T19:22:34+00:00" }, { "name": "firebase/php-jwt", @@ -168,16 +137,16 @@ }, { "name": "google/auth", - "version": "v1.6.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/googleapis/google-auth-library-php.git", - "reference": "6d5455b4c0f4a58b1f1b4bdf2ba49221123698b3" + "reference": "45635ac69d0b95f38885531d4ebcdfcb2ebb6f36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/6d5455b4c0f4a58b1f1b4bdf2ba49221123698b3", - "reference": "6d5455b4c0f4a58b1f1b4bdf2ba49221123698b3", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/45635ac69d0b95f38885531d4ebcdfcb2ebb6f36", + "reference": "45635ac69d0b95f38885531d4ebcdfcb2ebb6f36", "shasum": "" }, "require": { @@ -215,20 +184,20 @@ "google", "oauth2" ], - "time": "2019-10-01T18:35:05+00:00" + "time": "2019-10-29T20:13:04+00:00" }, { "name": "google/cloud-core", - "version": "v1.33.1", + "version": "v1.34.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-cloud-php-core.git", - "reference": "7b8773a5c6d501b3ed31655a8e243e1c17e2862e" + "reference": "52db21acb2da25d2d79e493842de58da7836c97f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-cloud-php-core/zipball/7b8773a5c6d501b3ed31655a8e243e1c17e2862e", - "reference": "7b8773a5c6d501b3ed31655a8e243e1c17e2862e", + "url": "https://api.github.com/repos/googleapis/google-cloud-php-core/zipball/52db21acb2da25d2d79e493842de58da7836c97f", + "reference": "52db21acb2da25d2d79e493842de58da7836c97f", "shasum": "" }, "require": { @@ -276,20 +245,20 @@ "Apache-2.0" ], "description": "Google Cloud PHP shared dependency, providing functionality useful to all components.", - "time": "2019-10-02T20:38:25+00:00" + "time": "2019-10-28T19:05:44+00:00" }, { "name": "google/cloud-storage", - "version": "v1.14.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-cloud-php-storage.git", - "reference": "7315239270318e618f025791b20ac8cc2586ccf0" + "reference": "003eb1a735d77f8196f816c4a921199d15c4a82c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-cloud-php-storage/zipball/7315239270318e618f025791b20ac8cc2586ccf0", - "reference": "7315239270318e618f025791b20ac8cc2586ccf0", + "url": "https://api.github.com/repos/googleapis/google-cloud-php-storage/zipball/003eb1a735d77f8196f816c4a921199d15c4a82c", + "reference": "003eb1a735d77f8196f816c4a921199d15c4a82c", "shasum": "" }, "require": { @@ -327,7 +296,7 @@ "Apache-2.0" ], "description": "Cloud Storage Client for PHP", - "time": "2019-08-07T20:57:43+00:00" + "time": "2019-10-28T19:05:44+00:00" }, { "name": "google/crc32", @@ -427,27 +396,28 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.3.3", + "version": "6.4.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + "reference": "0895c932405407fd3a7368b6910c09a24d26db11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11", + "reference": "0895c932405407fd3a7368b6910c09a24d26db11", "shasum": "" }, "require": { + "ext-json": "*", "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4", + "guzzlehttp/psr7": "^1.6.1", "php": ">=5.5" }, "require-dev": { "ext-curl": "*", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.0" + "psr/log": "^1.1" }, "suggest": { "psr/log": "Required for using the Log middleware" @@ -459,12 +429,12 @@ } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\": "src/" - } + }, + "files": [ + "src/functions_include.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -488,7 +458,7 @@ "rest", "web service" ], - "time": "2018-04-22T15:46:56+00:00" + "time": "2019-10-23T15:58:00+00:00" }, { "name": "guzzlehttp/promises", @@ -613,17 +583,67 @@ "time": "2019-07-01T23:21:34+00:00" }, { - "name": "intervention/image", - "version": "2.5.0", + "name": "http-interop/http-factory-guzzle", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/Intervention/image.git", - "reference": "39eaef720d082ecc54c64bf54541c55f10db546d" + "url": "https://github.com/http-interop/http-factory-guzzle.git", + "reference": "34861658efb9899a6618cef03de46e2a52c80fc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/39eaef720d082ecc54c64bf54541c55f10db546d", - "reference": "39eaef720d082ecc54c64bf54541c55f10db546d", + "url": "https://api.github.com/repos/http-interop/http-factory-guzzle/zipball/34861658efb9899a6618cef03de46e2a52c80fc0", + "reference": "34861658efb9899a6618cef03de46e2a52c80fc0", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^1.4.2", + "psr/http-factory": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "^1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.5", + "phpunit/phpunit": "^6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Factory\\Guzzle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "An HTTP Factory using Guzzle PSR7", + "keywords": [ + "factory", + "http", + "psr-17", + "psr-7" + ], + "time": "2018-07-31T19:32:56+00:00" + }, + { + "name": "intervention/image", + "version": "2.5.1", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image.git", + "reference": "abbf18d5ab8367f96b3205ca3c89fb2fa598c69e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image/zipball/abbf18d5ab8367f96b3205ca3c89fb2fa598c69e", + "reference": "abbf18d5ab8367f96b3205ca3c89fb2fa598c69e", "shasum": "" }, "require": { @@ -680,7 +700,65 @@ "thumbnail", "watermark" ], - "time": "2019-06-24T14:06:31+00:00" + "time": "2019-11-02T09:15:47+00:00" + }, + { + "name": "jeremeamia/superclosure", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/jeremeamia/super_closure.git", + "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/5707d5821b30b9a07acfb4d76949784aaa0e9ce9", + "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^1.2|^2.0|^3.0|^4.0", + "php": ">=5.4", + "symfony/polyfill-php56": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "SuperClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia", + "role": "Developer" + } + ], + "description": "Serialize Closure objects, including their context and binding", + "homepage": "https://github.com/jeremeamia/super_closure", + "keywords": [ + "closure", + "function", + "lambda", + "parser", + "serializable", + "serialize", + "tokenizer" + ], + "time": "2018-03-21T22:21:57+00:00" }, { "name": "league/flysystem", @@ -993,54 +1071,236 @@ "time": "2018-02-13T20:26:39+00:00" }, { - "name": "pimple/pimple", - "version": "v3.2.3", + "name": "nikic/php-parser", + "version": "v4.3.0", "source": { "type": "git", - "url": "https://github.com/silexphp/Pimple.git", - "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", - "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9a9981c347c5c49d6dfe5cf826bb882b824080dc", + "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/container": "^1.0" + "ext-tokenizer": "*", + "php": ">=7.0" }, "require-dev": { - "symfony/phpunit-bridge": "^3.2" + "ircmaxell/php-yacc": "0.0.5", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0" }, + "bin": [ + "bin/php-parse" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "4.3-dev" } }, "autoload": { - "psr-0": { - "Pimple": "src/" + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2019-11-08T13:50:10+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "540c27c86f663e20fe39a24cd72fa76cdb21d41a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/540c27c86f663e20fe39a24cd72fa76cdb21d41a", + "reference": "540c27c86f663e20fe39a24cd72fa76cdb21d41a", + "shasum": "" + }, + "require": { + "psr/container": "~1.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "phpunit/phpunit": "~4.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "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", + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", "keywords": [ - "container", - "dependency injection" + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" ], - "time": "2018-01-21T07:42:36+00:00" + "time": "2017-03-20T19:28:22+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.0.10", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "a6c813bf6b0d0bdeade3ac5a920e2c2a5b1a6ce3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/a6c813bf6b0d0bdeade3ac5a920e2c2a5b1a6ce3", + "reference": "a6c813bf6b0d0bdeade3ac5a920e2c2a5b1a6ce3", + "shasum": "" + }, + "require": { + "jeremeamia/superclosure": "^2.0", + "nikic/php-parser": "^2.0|^3.0|^4.0", + "php": ">=7.0.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "~1.0", + "ocramius/proxy-manager": "~2.0.2", + "phpstan/phpstan": "^0.9.2", + "phpunit/phpunit": "~6.4" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "psr-4": { + "DI\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "http://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "time": "2019-10-21T11:58:24+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "15678f7451c020226807f520efb867ad26fbbfcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/15678f7451c020226807f520efb867ad26fbbfcf", + "reference": "15678f7451c020226807f520efb867ad26fbbfcf", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "time": "2019-09-26T11:24:58+00:00" + }, + { + "name": "php-di/slim-bridge", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Slim-Bridge.git", + "reference": "3249b0da05550a5d8a393c9e27ded1133f9006ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Slim-Bridge/zipball/3249b0da05550a5d8a393c9e27ded1133f9006ea", + "reference": "3249b0da05550a5d8a393c9e27ded1133f9006ea", + "shasum": "" + }, + "require": { + "php": "~7.1", + "php-di/invoker": "^2.0.0", + "php-di/php-di": "^6.0.0", + "slim/slim": "^4.2.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0", + "zendframework/zend-diactoros": "^2.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DI\\Bridge\\Slim\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP-DI integration in Slim", + "time": "2019-09-08T20:19:58+00:00" }, { "name": "psr/cache", @@ -1137,6 +1397,58 @@ ], "time": "2017-02-14T16:28:37+00:00" }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.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 interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "time": "2019-04-30T12:38:16+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -1188,17 +1500,123 @@ "time": "2016-08-06T14:39:51+00:00" }, { - "name": "psr/log", - "version": "1.1.0", + "name": "psr/http-server-handler", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "time": "2018-10-30T16:46:14+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "time": "2018-10-30T17:12:04+00:00" + }, + { + "name": "psr/log", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", "shasum": "" }, "require": { @@ -1207,7 +1625,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -1232,7 +1650,7 @@ "psr", "psr-3" ], - "time": "2018-11-20T15:27:04+00:00" + "time": "2019-11-01T11:05:21+00:00" }, { "name": "ralouphie/getallheaders", @@ -1320,40 +1738,52 @@ }, { "name": "slim/slim", - "version": "3.12.2", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/slimphp/Slim.git", - "reference": "200c6143f15baa477601879b64ab2326847aac0b" + "reference": "26020e9a099e69b0b12918115894f7106364dcb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slimphp/Slim/zipball/200c6143f15baa477601879b64ab2326847aac0b", - "reference": "200c6143f15baa477601879b64ab2326847aac0b", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/26020e9a099e69b0b12918115894f7106364dcb7", + "reference": "26020e9a099e69b0b12918115894f7106364dcb7", "shasum": "" }, "require": { - "container-interop/container-interop": "^1.2", "ext-json": "*", - "ext-libxml": "*", - "ext-simplexml": "*", - "nikic/fast-route": "^1.0", - "php": ">=5.5.0", - "pimple/pimple": "^3.0", + "nikic/fast-route": "^1.3", + "php": "^7.1", "psr/container": "^1.0", - "psr/http-message": "^1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^4.0", - "squizlabs/php_codesniffer": "^2.5" + "ext-simplexml": "*", + "guzzlehttp/psr7": "^1.5", + "http-interop/http-factory-guzzle": "^1.0", + "nyholm/psr7": "^1.1", + "nyholm/psr7-server": "^0.3.0", + "phpspec/prophecy": "^1.8", + "phpstan/phpstan": "^0.11.5", + "phpunit/phpunit": "^7.5", + "slim/http": "^0.7", + "slim/psr7": "^0.3", + "squizlabs/php_codesniffer": "^3.4.2", + "zendframework/zend-diactoros": "^2.1" + }, + "suggest": { + "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", + "ext-xml": "Needed to support XML format in BodyParsingMiddleware", + "slim/psr7": "Slim PSR-7 implementation. See http://www.slimframework.com/docs/v4/start/installation.html for more information." }, "type": "library", "autoload": { "psr-4": { - "Slim\\": "Slim" + "Slim\\": "Slim", + "Slim\\Tests\\": "tests" } }, "notification-url": "https://packagist.org/downloads/", @@ -1376,6 +1806,11 @@ "email": "rob@akrabat.com", "homepage": "http://akrabat.com" }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + }, { "name": "Gabriel Manricks", "email": "gmanricks@me.com", @@ -1383,65 +1818,14 @@ } ], "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", - "homepage": "https://slimframework.com", + "homepage": "https://www.slimframework.com", "keywords": [ "api", "framework", "micro", "router" ], - "time": "2019-08-20T18:46:05+00:00" - }, - { - "name": "slim/twig-view", - "version": "2.5.0", - "source": { - "type": "git", - "url": "https://github.com/slimphp/Twig-View.git", - "reference": "06ef39b58d60b11a9546893fd0b7fff2bd901798" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slimphp/Twig-View/zipball/06ef39b58d60b11a9546893fd0b7fff2bd901798", - "reference": "06ef39b58d60b11a9546893fd0b7fff2bd901798", - "shasum": "" - }, - "require": { - "php": ">=5.5.0", - "psr/http-message": "^1.0", - "twig/twig": "^1.38|^2.7" - }, - "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": "2019-04-06T16:34:38+00:00" + "time": "2019-10-05T21:24:58+00:00" }, { "name": "spatie/dropbox-api", @@ -1478,15 +1862,15 @@ "authors": [ { "name": "Freek Van der Herten", - "role": "Developer", "email": "freek@spatie.be", - "homepage": "https://spatie.be" + "homepage": "https://spatie.be", + "role": "Developer" }, { "name": "Alex Vanderbist", - "role": "Developer", "email": "alex.vanderbist@gmail.com", - "homepage": "https://spatie.be" + "homepage": "https://spatie.be", + "role": "Developer" } ], "description": "A minimal implementation of Dropbox API v2", @@ -1717,17 +2101,125 @@ "time": "2019-08-06T08:03:45+00:00" }, { - "name": "twig/twig", - "version": "v2.12.1", + "name": "symfony/polyfill-php56", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "ddd4134af9bfc6dba4eff7c8447444ecc45b9ee5" + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/ddd4134af9bfc6dba4eff7c8447444ecc45b9ee5", - "reference": "ddd4134af9bfc6dba4eff7c8447444ecc45b9ee5", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/0e3b212e96a51338639d8ce175c046d7729c3403", + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "4317de1386717b4c22caed7725350a8887ab205c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4317de1386717b4c22caed7725350a8887ab205c", + "reference": "4317de1386717b4c22caed7725350a8887ab205c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "twig/twig", + "version": "v2.12.2", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "d761fd1f1c6b867ae09a7d8119a6d95d06dc44ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/d761fd1f1c6b867ae09a7d8119a6d95d06dc44ed", + "reference": "d761fd1f1c6b867ae09a7d8119a6d95d06dc44ed", "shasum": "" }, "require": { @@ -1781,30 +2273,30 @@ "keywords": [ "templating" ], - "time": "2019-10-17T07:34:53+00:00" + "time": "2019-11-11T16:52:09+00:00" } ], "packages-dev": [ { "name": "composer/xdebug-handler", - "version": "1.3.3", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f" + "reference": "cbe23383749496fe0f373345208b79568e4bc248" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f", - "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/cbe23383749496fe0f373345208b79568e4bc248", + "reference": "cbe23383749496fe0f373345208b79568e4bc248", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0", + "php": "^5.3.2 || ^7.0 || ^8.0", "psr/log": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" }, "type": "library", "autoload": { @@ -1822,12 +2314,12 @@ "email": "john-stevenson@blueyonder.co.uk" } ], - "description": "Restarts a process without xdebug.", + "description": "Restarts a process without Xdebug.", "keywords": [ "Xdebug", "performance" ], - "time": "2019-05-27T17:52:04+00:00" + "time": "2019-11-06T16:40:04+00:00" }, { "name": "jean85/pretty-package-versions", @@ -2275,16 +2767,16 @@ }, { "name": "nette/schema", - "version": "v1.0.0", + "version": "v1.0.1", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d" + "reference": "337117df1dade22e2ba1fdc4a4b832c1e9b06b76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d", - "reference": "6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d", + "url": "https://api.github.com/repos/nette/schema/zipball/337117df1dade22e2ba1fdc4a4b832c1e9b06b76", + "reference": "337117df1dade22e2ba1fdc4a4b832c1e9b06b76", "shasum": "" }, "require": { @@ -2328,7 +2820,7 @@ "config", "nette" ], - "time": "2019-04-03T15:53:25+00:00" + "time": "2019-10-31T20:52:19+00:00" }, { "name": "nette/utils", @@ -2407,57 +2899,6 @@ ], "time": "2019-10-21T20:40:16+00:00" }, - { - "name": "nikic/php-parser", - "version": "v4.2.4", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "97e59c7a16464196a8b9c77c47df68e4a39a45c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/97e59c7a16464196a8b9c77c47df68e4a39a45c4", - "reference": "97e59c7a16464196a8b9c77c47df68e4a39a45c4", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.2-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "time": "2019-09-01T07:51:21+00:00" - }, { "name": "ocramius/package-versions", "version": "1.4.0", @@ -2632,16 +3073,16 @@ }, { "name": "symfony/console", - "version": "v4.3.5", + "version": "v4.3.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "929ddf360d401b958f611d44e726094ab46a7369" + "reference": "d2e39dbddae68560fa6be0c576da6ad4e945b90d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/929ddf360d401b958f611d44e726094ab46a7369", - "reference": "929ddf360d401b958f611d44e726094ab46a7369", + "url": "https://api.github.com/repos/symfony/console/zipball/d2e39dbddae68560fa6be0c576da6ad4e945b90d", + "reference": "d2e39dbddae68560fa6be0c576da6ad4e945b90d", "shasum": "" }, "require": { @@ -2703,20 +3144,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-10-07T12:36:49+00:00" + "time": "2019-11-05T15:00:49+00:00" }, { "name": "symfony/finder", - "version": "v4.3.5", + "version": "v4.3.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "5e575faa95548d0586f6bedaeabec259714e44d1" + "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/5e575faa95548d0586f6bedaeabec259714e44d1", - "reference": "5e575faa95548d0586f6bedaeabec259714e44d1", + "url": "https://api.github.com/repos/symfony/finder/zipball/72a068f77e317ae77c0a0495236ad292cfb5ce6f", + "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f", "shasum": "" }, "require": { @@ -2752,7 +3193,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-09-16T11:29:48+00:00" + "time": "2019-10-30T12:53:54+00:00" }, { "name": "symfony/polyfill-php73", @@ -2814,16 +3255,16 @@ }, { "name": "symfony/service-contracts", - "version": "v1.1.7", + "version": "v1.1.8", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0" + "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffcde9615dc5bb4825b9f6aed07716f1f57faae0", - "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffc7f5692092df31515df2a5ecf3b7302b3ddacf", + "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf", "shasum": "" }, "require": { @@ -2868,7 +3309,7 @@ "interoperability", "standards" ], - "time": "2019-09-17T11:12:18+00:00" + "time": "2019-10-14T12:27:06+00:00" } ], "aliases": [], diff --git a/resources/templates/comp/navbar.twig b/resources/templates/comp/navbar.twig index b3468b1..0831779 100644 --- a/resources/templates/comp/navbar.twig +++ b/resources/templates/comp/navbar.twig @@ -7,18 +7,18 @@ - {% if config.displayErrorDetails %} + {% if exception is not null %}