From 3c088292050982505726f5136ff4d0f1a918b879 Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Thu, 18 Jan 2018 19:01:34 +0100 Subject: Added Http\Response and Psr7{Request,Response} --- composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'composer.json') diff --git a/composer.json b/composer.json index 9c0602b6..df9ee57b 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,9 @@ "psr/container": "^1.0", "psr/log": "^1.0", "symfony/http-foundation": "^3.3", - "twbs/bootstrap": "^3.3" + "symfony/psr-http-message-bridge": "^1.0", + "twbs/bootstrap": "^3.3", + "zendframework/zend-diactoros": "^1.7" }, "require-dev": { "filp/whoops": "^2.1", -- cgit v1.2.3-54-g00ecf From 20c03a155d2017101a098cefa602116a4a331d71 Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Tue, 7 Aug 2018 03:18:22 +0200 Subject: Implemented PSR-15 middleware handler --- composer.json | 2 + locale/de_DE.UTF-8/LC_MESSAGES/default.mo | Bin 44974 -> 44999 bytes locale/de_DE.UTF-8/LC_MESSAGES/default.po | 14 +- public/index.php | 263 ++------------------------- src/Middleware/Dispatcher.php | 110 ++++++++++++ src/Middleware/ExceptionHandler.php | 48 +++++ src/Middleware/LegacyMiddleware.php | 284 ++++++++++++++++++++++++++++++ src/Middleware/NotFoundResponse.php | 47 +++++ src/Middleware/SendResponseHandler.php | 45 +++++ src/helpers.php | 30 +++- 10 files changed, 591 insertions(+), 252 deletions(-) create mode 100644 src/Middleware/Dispatcher.php create mode 100644 src/Middleware/ExceptionHandler.php create mode 100644 src/Middleware/LegacyMiddleware.php create mode 100644 src/Middleware/NotFoundResponse.php create mode 100644 src/Middleware/SendResponseHandler.php (limited to 'composer.json') diff --git a/composer.json b/composer.json index df9ee57b..911df1f5 100644 --- a/composer.json +++ b/composer.json @@ -15,11 +15,13 @@ ], "require": { "php": ">=7.0.0", + "ext-gettext": "*", "erusev/parsedown": "^1.6", "illuminate/container": "5.5.*", "illuminate/database": "5.5.*", "illuminate/support": "^5.5", "psr/container": "^1.0", + "psr/http-server-middleware": "^1.0", "psr/log": "^1.0", "symfony/http-foundation": "^3.3", "symfony/psr-http-message-bridge": "^1.0", diff --git a/locale/de_DE.UTF-8/LC_MESSAGES/default.mo b/locale/de_DE.UTF-8/LC_MESSAGES/default.mo index 3939e870..8b3b7dcf 100644 Binary files a/locale/de_DE.UTF-8/LC_MESSAGES/default.mo and b/locale/de_DE.UTF-8/LC_MESSAGES/default.mo differ diff --git a/locale/de_DE.UTF-8/LC_MESSAGES/default.po b/locale/de_DE.UTF-8/LC_MESSAGES/default.po index 7187b32c..49973ca9 100644 --- a/locale/de_DE.UTF-8/LC_MESSAGES/default.po +++ b/locale/de_DE.UTF-8/LC_MESSAGES/default.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Engelsystem 2.0\n" "POT-Creation-Date: 2017-12-29 19:01+0100\n" -"PO-Revision-Date: 2017-12-29 19:03+0100\n" +"PO-Revision-Date: 2018-08-07 02:00+0200\n" "Last-Translator: msquare \n" "Language-Team: \n" "Language: de_DE\n" @@ -2874,13 +2874,13 @@ msgstr "" "keine Nummer hast, bitte einfach \"-\" angeben." #: /Users/msquare/workspace/projects/engelsystem/public/index.php:218 -msgid "No Access" -msgstr "Kein Zugriff" +msgid "Page not found" +msgstr "Seite nicht gefunden" #: /Users/msquare/workspace/projects/engelsystem/public/index.php:219 msgid "" -"You don't have permission to view this page . You probably have to sign in " -"or register in order to gain access!" +"This page could not be found or you don't have permission to view it. You " +"probably have to sign in or register in order to gain access!" msgstr "" -"Du hast keinen Zugriff auf diese Seite. Registriere Dich und logge Dich " -"bitte ein, um Zugriff zu erhalten!" +"Diese Seite existiert nicht oder Du hast keinen Zugriff. Melde Dich an um Zugriff " +"zu erhalten!" diff --git a/public/index.php b/public/index.php index db4c4294..88e57252 100644 --- a/public/index.php +++ b/public/index.php @@ -1,252 +1,27 @@ get('psr7.request'); -/** @var Request $request */ -$request = $app->get('request'); -$page = $request->query->get('p'); -if (empty($page)) { - $page = $request->path(); - $page = str_replace('-', '_', $page); -} -if ($page == '/') { - $page = isset($user) ? 'news' : 'login'; -} - -if ( - preg_match('/^\w*$/i', $page) - && ( - in_array($page, $free_pages) - || (isset($privileges) && in_array($page, $privileges)) - ) -) { - $title = $page; - - switch ($page) { - case 'api': - error('Api disabled temporarily.'); - redirect(page_link_to()); - break; - case 'ical': - require_once realpath(__DIR__ . '/../includes/pages/user_ical.php'); - user_ical(); - break; - case 'atom': - require_once realpath(__DIR__ . '/../includes/pages/user_atom.php'); - user_atom(); - break; - case 'shifts_json_export': - require_once realpath(__DIR__ . '/../includes/controller/shifts_controller.php'); - shifts_json_export_controller(); - break; - case 'shifts_json_export_all': - require_once realpath(__DIR__ . '/../includes/controller/shifts_controller.php'); - shifts_json_export_all_controller(); - break; - case 'stats': - require_once realpath(__DIR__ . '/../includes/pages/guest_stats.php'); - guest_stats(); - break; - case 'user_password_recovery': - require_once realpath(__DIR__ . '/../includes/controller/users_controller.php'); - $title = user_password_recovery_title(); - $content = user_password_recovery_controller(); - break; - case 'public_dashboard': - list($title, $content) = public_dashboard_controller(); - break; - case 'angeltypes': - list($title, $content) = angeltypes_controller(); - break; - case 'shift_entries': - list($title, $content) = shift_entries_controller(); - break; - case 'shifts': - list($title, $content) = shifts_controller(); - break; - case 'users': - list($title, $content) = users_controller(); - break; - case 'user_angeltypes': - list($title, $content) = user_angeltypes_controller(); - break; - case 'user_driver_licenses': - list($title, $content) = user_driver_licenses_controller(); - break; - case 'shifttypes': - list($title, $content) = shifttypes_controller(); - break; - case 'admin_event_config': - list($title, $content) = event_config_edit_controller(); - break; - case 'rooms': - list($title, $content) = rooms_controller(); - break; - case 'news': - $title = news_title(); - $content = user_news(); - break; - case 'news_comments': - require_once realpath(__DIR__ . '/../includes/pages/user_news.php'); - $title = user_news_comments_title(); - $content = user_news_comments(); - break; - case 'user_meetings': - $title = meetings_title(); - $content = user_meetings(); - break; - case 'user_myshifts': - $title = myshifts_title(); - $content = user_myshifts(); - break; - case 'user_shifts': - $title = shifts_title(); - $content = user_shifts(); - break; - case 'user_worklog': - list($title, $content) = user_worklog_controller(); - break; - case 'user_messages': - $title = messages_title(); - $content = user_messages(); - break; - case 'user_questions': - $title = questions_title(); - $content = user_questions(); - break; - case 'user_settings': - $title = settings_title(); - $content = user_settings(); - break; - case 'login': - $title = login_title(); - $content = guest_login(); - break; - case 'register': - $title = register_title(); - $content = guest_register(); - break; - case 'logout': - $title = logout_title(); - $content = guest_logout(); - break; - case 'admin_questions': - $title = admin_questions_title(); - $content = admin_questions(); - break; - case 'admin_user': - $title = admin_user_title(); - $content = admin_user(); - break; - case 'admin_arrive': - $title = admin_arrive_title(); - $content = admin_arrive(); - break; - case 'admin_active': - $title = admin_active_title(); - $content = admin_active(); - break; - case 'admin_free': - $title = admin_free_title(); - $content = admin_free(); - break; - case 'admin_news': - require_once realpath(__DIR__ . '/../includes/pages/admin_news.php'); - $content = admin_news(); - break; - case 'admin_rooms': - $title = admin_rooms_title(); - $content = admin_rooms(); - break; - case 'admin_groups': - $title = admin_groups_title(); - $content = admin_groups(); - break; - case 'admin_import': - $title = admin_import_title(); - $content = admin_import(); - break; - case 'admin_shifts': - $title = admin_shifts_title(); - $content = admin_shifts(); - break; - case 'admin_log': - $title = admin_log_title(); - $content = admin_log(); - break; - case 'credits': - require_once realpath(__DIR__ . '/../includes/pages/guest_credits.php'); - $title = credits_title(); - $content = guest_credits(); - break; - default: - require_once realpath(__DIR__ . '/../includes/pages/guest_start.php'); - $content = guest_start(); - break; - } -} else { - // Wenn schon eingeloggt, keine-Berechtigung-Seite anzeigen - if (isset($user)) { - $title = _('No Access'); - $content = _('You don\'t have permission to view this page . You probably have to sign in or register in order to gain access!'); - } else { - // Sonst zur Loginseite leiten - redirect(page_link_to('login')); - } -} - -$event_config = EventConfig(); - -$parameters = [ - 'key' => (isset($user) ? $user['api_key'] : ''), -]; -if ($page == 'user_meetings') { - $parameters['meetings'] = 1; -} - -echo view(__DIR__ . '/../templates/layout.html', [ - 'theme' => isset($user) ? $user['color'] : config('theme'), - 'title' => $title, - 'atom_link' => ($page == 'news' || $page == 'user_meetings') - ? ' ' - : '', - 'start_page_url' => page_link_to('/'), - 'credits_url' => page_link_to('credits'), - 'menu' => make_menu(), - 'content' => msg() . $content, - 'header_toolbar' => header_toolbar(), - 'faq_url' => config('faq_url'), - 'contact_email' => config('contact_email'), - 'locale' => locale(), - 'event_info' => EventConfig_info($event_config) . '
' +$dispatcher = new Dispatcher([ + SendResponseHandler::class, + ExceptionHandler::class, + LegacyMiddleware::class, + NotFoundResponse::class, ]); +$dispatcher->setContainer($app); + +$dispatcher->handle($request); diff --git a/src/Middleware/Dispatcher.php b/src/Middleware/Dispatcher.php new file mode 100644 index 00000000..774040fb --- /dev/null +++ b/src/Middleware/Dispatcher.php @@ -0,0 +1,110 @@ +stack = $stack; + $this->container = $container; + } + + /** + * Process an incoming server request and return a response, optionally delegating + * response creation to a handler. + * + * Could be used to group middleware + * + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + */ + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + $this->next = $handler; + + return $this->handle($request); + } + + /** + * Handle the request and return a response. + * + * It calls all configured middleware and handles their response + * + * @param ServerRequestInterface $request + * @return ResponseInterface + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + $middleware = array_shift($this->stack); + + if (!$middleware) { + if ($this->next) { + return $this->next->handle($request); + } + + throw new LogicException('Middleware queue is empty'); + } + + if (is_string($middleware)) { + $middleware = $this->resolveMiddleware($middleware); + } + + if (!$middleware instanceof MiddlewareInterface) { + throw new InvalidArgumentException('Middleware is no instance of ' . MiddlewareInterface::class); + } + + return $middleware->process($request, $this); + } + + /** + * Resolve the middleware with the container + * + * @param string $middleware + * @return MiddlewareInterface + */ + protected function resolveMiddleware($middleware) + { + if (!$this->container instanceof Application) { + throw new InvalidArgumentException('Unable to resolve middleware ' . $middleware); + } + + if ($this->container->has($middleware)) { + return $this->container->get($middleware); + } + + return $this->container->make($middleware); + } + + /** + * @param Application $container + */ + public function setContainer(Application $container) + { + $this->container = $container; + } +} diff --git a/src/Middleware/ExceptionHandler.php b/src/Middleware/ExceptionHandler.php new file mode 100644 index 00000000..a5db0337 --- /dev/null +++ b/src/Middleware/ExceptionHandler.php @@ -0,0 +1,48 @@ +container = $container; + } + + /** + * Handles any exceptions that occurred inside other middleware while returning it to the default response handler + * + * Should be added at the beginning + * + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + */ + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + try { + return $handler->handle($request); + } catch (\Throwable $e) { + /** @var ExceptionsHandler $handler */ + $handler = $this->container->get('error.handler'); + $content = $handler->exceptionHandler($e, true); + + return response($content, 500); + } + } +} diff --git a/src/Middleware/LegacyMiddleware.php b/src/Middleware/LegacyMiddleware.php new file mode 100644 index 00000000..41b2e471 --- /dev/null +++ b/src/Middleware/LegacyMiddleware.php @@ -0,0 +1,284 @@ +container = $container; + } + + /** + * Handle the request the old way + * + * Should be used before a 404 is send + * + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + */ + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + global $user; + global $privileges; + + /** @var Request $appRequest */ + $appRequest = $this->container->get('request'); + + // Default page content + $content = ''; + + $page = $appRequest->query->get('p'); + if (empty($page)) { + $page = $appRequest->path(); + $page = str_replace('-', '_', $page); + } + if ($page == '/') { + $page = isset($user) ? 'news' : 'login'; + } + + if ( + preg_match('/^\w+$/i', $page) + && ( + in_array($page, $this->free_pages) + || (isset($privileges) && in_array($page, $privileges)) + ) + ) { + $title = $page; + + switch ($page) { + case 'api': + error('Api disabled temporarily.'); + redirect(page_link_to()); + break; + case 'ical': + require_once realpath(__DIR__ . '/../includes/pages/user_ical.php'); + user_ical(); + break; + case 'atom': + require_once realpath(__DIR__ . '/../includes/pages/user_atom.php'); + user_atom(); + break; + case 'shifts_json_export': + require_once realpath(__DIR__ . '/../includes/controller/shifts_controller.php'); + shifts_json_export_controller(); + break; + case 'shifts_json_export_all': + require_once realpath(__DIR__ . '/../includes/controller/shifts_controller.php'); + shifts_json_export_all_controller(); + break; + case 'stats': + require_once realpath(__DIR__ . '/../includes/pages/guest_stats.php'); + guest_stats(); + break; + case 'user_password_recovery': + require_once realpath(__DIR__ . '/../includes/controller/users_controller.php'); + $title = user_password_recovery_title(); + $content = user_password_recovery_controller(); + break; + case 'public_dashboard': + list($title, $content) = public_dashboard_controller(); + break; + case 'angeltypes': + list($title, $content) = angeltypes_controller(); + break; + case 'shift_entries': + list($title, $content) = shift_entries_controller(); + break; + case 'shifts': + list($title, $content) = shifts_controller(); + break; + case 'users': + list($title, $content) = users_controller(); + break; + case 'user_angeltypes': + list($title, $content) = user_angeltypes_controller(); + break; + case 'user_driver_licenses': + list($title, $content) = user_driver_licenses_controller(); + break; + case 'shifttypes': + list($title, $content) = shifttypes_controller(); + break; + case 'admin_event_config': + list($title, $content) = event_config_edit_controller(); + break; + case 'rooms': + list($title, $content) = rooms_controller(); + break; + case 'news': + $title = news_title(); + $content = user_news(); + break; + case 'news_comments': + require_once realpath(__DIR__ . '/../includes/pages/user_news.php'); + $title = user_news_comments_title(); + $content = user_news_comments(); + break; + case 'user_meetings': + $title = meetings_title(); + $content = user_meetings(); + break; + case 'user_myshifts': + $title = myshifts_title(); + $content = user_myshifts(); + break; + case 'user_shifts': + $title = shifts_title(); + $content = user_shifts(); + break; + case 'user_worklog': + list($title, $content) = user_worklog_controller(); + break; + case 'user_messages': + $title = messages_title(); + $content = user_messages(); + break; + case 'user_questions': + $title = questions_title(); + $content = user_questions(); + break; + case 'user_settings': + $title = settings_title(); + $content = user_settings(); + break; + case 'login': + $title = login_title(); + $content = guest_login(); + break; + case 'register': + $title = register_title(); + $content = guest_register(); + break; + case 'logout': + $title = logout_title(); + $content = guest_logout(); + break; + case 'admin_questions': + $title = admin_questions_title(); + $content = admin_questions(); + break; + case 'admin_user': + $title = admin_user_title(); + $content = admin_user(); + break; + case 'admin_arrive': + $title = admin_arrive_title(); + $content = admin_arrive(); + break; + case 'admin_active': + $title = admin_active_title(); + $content = admin_active(); + break; + case 'admin_free': + $title = admin_free_title(); + $content = admin_free(); + break; + case 'admin_news': + require_once realpath(__DIR__ . '/../includes/pages/admin_news.php'); + $content = admin_news(); + break; + case 'admin_rooms': + $title = admin_rooms_title(); + $content = admin_rooms(); + break; + case 'admin_groups': + $title = admin_groups_title(); + $content = admin_groups(); + break; + case 'admin_import': + $title = admin_import_title(); + $content = admin_import(); + break; + case 'admin_shifts': + $title = admin_shifts_title(); + $content = admin_shifts(); + break; + case 'admin_log': + $title = admin_log_title(); + $content = admin_log(); + break; + case 'credits': + require_once realpath(__DIR__ . '/../includes/pages/guest_credits.php'); + $title = credits_title(); + $content = guest_credits(); + break; + default: + require_once realpath(__DIR__ . '/../includes/pages/guest_start.php'); + $content = guest_start(); + break; + } + } else { + return $handler->handle($request); + } + + if (empty($title) and empty($content)) { + return $handler->handle($request); + } + + $event_config = EventConfig(); + + $parameters = [ + 'key' => (isset($user) ? $user['api_key'] : ''), + ]; + if ($page == 'user_meetings') { + $parameters['meetings'] = 1; + } + + return response(view(__DIR__ . '/../../templates/layout.html', [ + 'theme' => isset($user) ? $user['color'] : config('theme'), + 'title' => $title, + 'atom_link' => ($page == 'news' || $page == 'user_meetings') + ? ' ' + : '', + 'start_page_url' => page_link_to('/'), + 'credits_url' => page_link_to('credits'), + 'menu' => make_menu(), + 'content' => msg() . $content, + 'header_toolbar' => header_toolbar(), + 'faq_url' => config('faq_url'), + 'contact_email' => config('contact_email'), + 'locale' => locale(), + 'event_info' => EventConfig_info($event_config) . '
' + ])); + } +} diff --git a/src/Middleware/NotFoundResponse.php b/src/Middleware/NotFoundResponse.php new file mode 100644 index 00000000..c5d51d2d --- /dev/null +++ b/src/Middleware/NotFoundResponse.php @@ -0,0 +1,47 @@ + isset($user) ? $user['color'] : config('theme'), + 'title' => _('Page not found'), + 'atom_link' => '', + 'start_page_url' => page_link_to('/'), + 'credits_url' => page_link_to('credits'), + 'menu' => make_menu(), + 'content' => msg() . $content, + 'header_toolbar' => header_toolbar(), + 'faq_url' => config('faq_url'), + 'contact_email' => config('contact_email'), + 'locale' => locale(), + 'event_info' => EventConfig_info($event_config) . '
' + ]), 404); + } +} diff --git a/src/Middleware/SendResponseHandler.php b/src/Middleware/SendResponseHandler.php new file mode 100644 index 00000000..06406fe0 --- /dev/null +++ b/src/Middleware/SendResponseHandler.php @@ -0,0 +1,45 @@ +handle($request); + + if (!headers_sent()) { + header(sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ), true, $response->getStatusCode()); + + foreach ($response->getHeaders() as $name => $values) { + foreach ($values as $value) { + header($name . ': ' . $value, false); + } + } + } + + echo $response->getBody(); + return $response; + } +} diff --git a/src/helpers.php b/src/helpers.php index c3c727ec..2a90dcde 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -6,13 +6,15 @@ use Engelsystem\Config\Config; use Engelsystem\Http\Request; use Engelsystem\Renderer\Renderer; use Engelsystem\Routing\UrlGenerator; +use Psr\Http\Message\ResponseInterface; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Zend\Diactoros\Stream; /** * Get the global app instance * * @param string $id - * @return mixed + * @return mixed|Application */ function app($id = null) { @@ -80,6 +82,32 @@ function request($key = null, $default = null) return $request->input($key, $default); } +/** + * @param string $content + * @param int $status + * @param array $headers + * @return ResponseInterface + */ +function response($content = '', $status = 200, $headers = []) +{ + /** @var ResponseInterface $response */ + $response = app('psr7.response'); + + /** @var Stream $stream */ + $stream = app()->make(Stream::class, ['stream' => 'php://memory', 'mode' => 'wb+']); + $stream->write($content); + $stream->rewind(); + + $response = $response + ->withBody($stream) + ->withStatus($status); + foreach ($headers as $key => $value) { + $response = $response->withAddedHeader($key, $value); + } + + return $response; +} + /** * @param string $key * @param mixed $default -- cgit v1.2.3-54-g00ecf From f3b3b6683ca90b70ec4d4daae002dc0caac9ebdd Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Sat, 11 Aug 2018 23:46:28 +0200 Subject: Added middleware tests --- composer.json | 2 + includes/helper/message_helper.php | 3 +- src/Middleware/Dispatcher.php | 6 +- src/Middleware/LegacyMiddleware.php | 356 +++++++++++---------- src/Middleware/NotFoundResponse.php | 19 +- src/Middleware/SendResponseHandler.php | 30 +- tests/Unit/Middleware/DispatcherTest.php | 230 +++++++++++++ tests/Unit/Middleware/ExceptionHandlerTest.php | 58 ++++ tests/Unit/Middleware/LegacyMiddlewareTest.php | 85 +++++ tests/Unit/Middleware/NotFoundResponseTest.php | 39 +++ tests/Unit/Middleware/SendResponseHandlerTest.php | 71 ++++ .../Middleware/Stub/ExceptionMiddlewareHandler.php | 23 ++ tests/Unit/Middleware/Stub/NotARealMiddleware.php | 8 + .../Middleware/Stub/ReturnResponseMiddleware.php | 36 +++ .../Stub/ReturnResponseMiddlewareHandler.php | 30 ++ 15 files changed, 811 insertions(+), 185 deletions(-) create mode 100644 tests/Unit/Middleware/DispatcherTest.php create mode 100644 tests/Unit/Middleware/ExceptionHandlerTest.php create mode 100644 tests/Unit/Middleware/LegacyMiddlewareTest.php create mode 100644 tests/Unit/Middleware/NotFoundResponseTest.php create mode 100644 tests/Unit/Middleware/SendResponseHandlerTest.php create mode 100644 tests/Unit/Middleware/Stub/ExceptionMiddlewareHandler.php create mode 100644 tests/Unit/Middleware/Stub/NotARealMiddleware.php create mode 100644 tests/Unit/Middleware/Stub/ReturnResponseMiddleware.php create mode 100644 tests/Unit/Middleware/Stub/ReturnResponseMiddlewareHandler.php (limited to 'composer.json') diff --git a/composer.json b/composer.json index 911df1f5..283bc30a 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,8 @@ "require": { "php": ">=7.0.0", "ext-gettext": "*", + "ext-json": "*", + "ext-PDO": "*", "erusev/parsedown": "^1.6", "illuminate/container": "5.5.*", "illuminate/database": "5.5.*", diff --git a/includes/helper/message_helper.php b/includes/helper/message_helper.php index 3d2b663a..388a878c 100644 --- a/includes/helper/message_helper.php +++ b/includes/helper/message_helper.php @@ -73,8 +73,6 @@ function success($msg, $immediately = false) */ function alert($class, $msg, $immediately = false) { - $session = session(); - if (empty($msg)) { return ''; } @@ -83,6 +81,7 @@ function alert($class, $msg, $immediately = false) return '
' . $msg . '
'; } + $session = session(); $message = $session->get('msg', ''); $message .= alert($class, $msg, true); $session->set('msg', $message); diff --git a/src/Middleware/Dispatcher.php b/src/Middleware/Dispatcher.php index 774040fb..f2a5b5d5 100644 --- a/src/Middleware/Dispatcher.php +++ b/src/Middleware/Dispatcher.php @@ -12,7 +12,7 @@ use Psr\Http\Server\RequestHandlerInterface; class Dispatcher implements MiddlewareInterface, RequestHandlerInterface { - /** @var MiddlewareInterface[] */ + /** @var MiddlewareInterface[]|string[] */ protected $stack; /** @var Application */ @@ -22,8 +22,8 @@ class Dispatcher implements MiddlewareInterface, RequestHandlerInterface protected $next; /** - * @param MiddlewareInterface[] $stack - * @param Application|null $container + * @param MiddlewareInterface[]|string[] $stack + * @param Application|null $container */ public function __construct($stack = [], Application $container = null) { diff --git a/src/Middleware/LegacyMiddleware.php b/src/Middleware/LegacyMiddleware.php index 41b2e471..714141de 100644 --- a/src/Middleware/LegacyMiddleware.php +++ b/src/Middleware/LegacyMiddleware.php @@ -3,6 +3,7 @@ namespace Engelsystem\Middleware; use Engelsystem\Http\Request; +use Engelsystem\Http\Response; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -61,10 +62,6 @@ class LegacyMiddleware implements MiddlewareInterface /** @var Request $appRequest */ $appRequest = $this->container->get('request'); - - // Default page content - $content = ''; - $page = $appRequest->query->get('p'); if (empty($page)) { $page = $appRequest->path(); @@ -74,187 +71,202 @@ class LegacyMiddleware implements MiddlewareInterface $page = isset($user) ? 'news' : 'login'; } + $title = $content = ''; if ( - preg_match('/^\w+$/i', $page) + preg_match('~^\w+$~i', $page) && ( in_array($page, $this->free_pages) || (isset($privileges) && in_array($page, $privileges)) ) ) { - $title = $page; - - switch ($page) { - case 'api': - error('Api disabled temporarily.'); - redirect(page_link_to()); - break; - case 'ical': - require_once realpath(__DIR__ . '/../includes/pages/user_ical.php'); - user_ical(); - break; - case 'atom': - require_once realpath(__DIR__ . '/../includes/pages/user_atom.php'); - user_atom(); - break; - case 'shifts_json_export': - require_once realpath(__DIR__ . '/../includes/controller/shifts_controller.php'); - shifts_json_export_controller(); - break; - case 'shifts_json_export_all': - require_once realpath(__DIR__ . '/../includes/controller/shifts_controller.php'); - shifts_json_export_all_controller(); - break; - case 'stats': - require_once realpath(__DIR__ . '/../includes/pages/guest_stats.php'); - guest_stats(); - break; - case 'user_password_recovery': - require_once realpath(__DIR__ . '/../includes/controller/users_controller.php'); - $title = user_password_recovery_title(); - $content = user_password_recovery_controller(); - break; - case 'public_dashboard': - list($title, $content) = public_dashboard_controller(); - break; - case 'angeltypes': - list($title, $content) = angeltypes_controller(); - break; - case 'shift_entries': - list($title, $content) = shift_entries_controller(); - break; - case 'shifts': - list($title, $content) = shifts_controller(); - break; - case 'users': - list($title, $content) = users_controller(); - break; - case 'user_angeltypes': - list($title, $content) = user_angeltypes_controller(); - break; - case 'user_driver_licenses': - list($title, $content) = user_driver_licenses_controller(); - break; - case 'shifttypes': - list($title, $content) = shifttypes_controller(); - break; - case 'admin_event_config': - list($title, $content) = event_config_edit_controller(); - break; - case 'rooms': - list($title, $content) = rooms_controller(); - break; - case 'news': - $title = news_title(); - $content = user_news(); - break; - case 'news_comments': - require_once realpath(__DIR__ . '/../includes/pages/user_news.php'); - $title = user_news_comments_title(); - $content = user_news_comments(); - break; - case 'user_meetings': - $title = meetings_title(); - $content = user_meetings(); - break; - case 'user_myshifts': - $title = myshifts_title(); - $content = user_myshifts(); - break; - case 'user_shifts': - $title = shifts_title(); - $content = user_shifts(); - break; - case 'user_worklog': - list($title, $content) = user_worklog_controller(); - break; - case 'user_messages': - $title = messages_title(); - $content = user_messages(); - break; - case 'user_questions': - $title = questions_title(); - $content = user_questions(); - break; - case 'user_settings': - $title = settings_title(); - $content = user_settings(); - break; - case 'login': - $title = login_title(); - $content = guest_login(); - break; - case 'register': - $title = register_title(); - $content = guest_register(); - break; - case 'logout': - $title = logout_title(); - $content = guest_logout(); - break; - case 'admin_questions': - $title = admin_questions_title(); - $content = admin_questions(); - break; - case 'admin_user': - $title = admin_user_title(); - $content = admin_user(); - break; - case 'admin_arrive': - $title = admin_arrive_title(); - $content = admin_arrive(); - break; - case 'admin_active': - $title = admin_active_title(); - $content = admin_active(); - break; - case 'admin_free': - $title = admin_free_title(); - $content = admin_free(); - break; - case 'admin_news': - require_once realpath(__DIR__ . '/../includes/pages/admin_news.php'); - $content = admin_news(); - break; - case 'admin_rooms': - $title = admin_rooms_title(); - $content = admin_rooms(); - break; - case 'admin_groups': - $title = admin_groups_title(); - $content = admin_groups(); - break; - case 'admin_import': - $title = admin_import_title(); - $content = admin_import(); - break; - case 'admin_shifts': - $title = admin_shifts_title(); - $content = admin_shifts(); - break; - case 'admin_log': - $title = admin_log_title(); - $content = admin_log(); - break; - case 'credits': - require_once realpath(__DIR__ . '/../includes/pages/guest_credits.php'); - $title = credits_title(); - $content = guest_credits(); - break; - default: - require_once realpath(__DIR__ . '/../includes/pages/guest_start.php'); - $content = guest_start(); - break; - } - } else { - return $handler->handle($request); + list($title, $content) = $this->loadPage($page); } if (empty($title) and empty($content)) { return $handler->handle($request); } - $event_config = EventConfig(); + return $this->renderPage($page, $title, $content); + } + + /** + * Get the legacy page content and title + * + * @param string $page + * @return array ['title', 'content'] + * @codeCoverageIgnore + */ + protected function loadPage($page) + { + $title = ucfirst($page); + switch ($page) { + /** @noinspection PhpMissingBreakStatementInspection */ + case 'api': + error('Api disabled temporarily.'); + redirect(page_link_to()); + /** @noinspection PhpMissingBreakStatementInspection */ + case 'ical': + require_once realpath(__DIR__ . '/../../includes/pages/user_ical.php'); + user_ical(); + /** @noinspection PhpMissingBreakStatementInspection */ + case 'atom': + require_once realpath(__DIR__ . '/../../includes/pages/user_atom.php'); + user_atom(); + /** @noinspection PhpMissingBreakStatementInspection */ + case 'shifts_json_export': + require_once realpath(__DIR__ . '/../../includes/controller/shifts_controller.php'); + shifts_json_export_controller(); + /** @noinspection PhpMissingBreakStatementInspection */ + case 'shifts_json_export_all': + require_once realpath(__DIR__ . '/../../includes/controller/shifts_controller.php'); + shifts_json_export_all_controller(); + /** @noinspection PhpMissingBreakStatementInspection */ + case 'stats': + require_once realpath(__DIR__ . '/../../includes/pages/guest_stats.php'); + guest_stats(); + case 'user_password_recovery': + require_once realpath(__DIR__ . '/../../includes/controller/users_controller.php'); + $title = user_password_recovery_title(); + $content = user_password_recovery_controller(); + return [$title, $content]; + case 'public_dashboard': + return public_dashboard_controller(); + case 'angeltypes': + return angeltypes_controller(); + case 'shift_entries': + return shift_entries_controller(); + case 'shifts': + return shifts_controller(); + case 'users': + return users_controller(); + case 'user_angeltypes': + return user_angeltypes_controller(); + case 'user_driver_licenses': + return user_driver_licenses_controller(); + case 'shifttypes': + list($title, $content) = shifttypes_controller(); + return [$title, $content]; + case 'admin_event_config': + list($title, $content) = event_config_edit_controller(); + return [$title, $content]; + case 'rooms': + return rooms_controller(); + case 'news': + $title = news_title(); + $content = user_news(); + return [$title, $content]; + case 'news_comments': + require_once realpath(__DIR__ . '/../../includes/pages/user_news.php'); + $title = user_news_comments_title(); + $content = user_news_comments(); + return [$title, $content]; + case 'user_meetings': + $title = meetings_title(); + $content = user_meetings(); + return [$title, $content]; + case 'user_myshifts': + $title = myshifts_title(); + $content = user_myshifts(); + return [$title, $content]; + case 'user_shifts': + $title = shifts_title(); + $content = user_shifts(); + return [$title, $content]; + case 'user_worklog': + return user_worklog_controller(); + case 'user_messages': + $title = messages_title(); + $content = user_messages(); + return [$title, $content]; + case 'user_questions': + $title = questions_title(); + $content = user_questions(); + return [$title, $content]; + case 'user_settings': + $title = settings_title(); + $content = user_settings(); + return [$title, $content]; + case 'login': + $title = login_title(); + $content = guest_login(); + return [$title, $content]; + case 'register': + $title = register_title(); + $content = guest_register(); + return [$title, $content]; + case 'logout': + $title = logout_title(); + $content = guest_logout(); + return [$title, $content]; + case 'admin_questions': + $title = admin_questions_title(); + $content = admin_questions(); + return [$title, $content]; + case 'admin_user': + $title = admin_user_title(); + $content = admin_user(); + return [$title, $content]; + case 'admin_arrive': + $title = admin_arrive_title(); + $content = admin_arrive(); + return [$title, $content]; + case 'admin_active': + $title = admin_active_title(); + $content = admin_active(); + return [$title, $content]; + case 'admin_free': + $title = admin_free_title(); + $content = admin_free(); + return [$title, $content]; + case 'admin_news': + require_once realpath(__DIR__ . '/../../includes/pages/admin_news.php'); + $content = admin_news(); + return [$title, $content]; + case 'admin_rooms': + $title = admin_rooms_title(); + $content = admin_rooms(); + return [$title, $content]; + case 'admin_groups': + $title = admin_groups_title(); + $content = admin_groups(); + return [$title, $content]; + case 'admin_import': + $title = admin_import_title(); + $content = admin_import(); + return [$title, $content]; + case 'admin_shifts': + $title = admin_shifts_title(); + $content = admin_shifts(); + return [$title, $content]; + case 'admin_log': + $title = admin_log_title(); + $content = admin_log(); + return [$title, $content]; + case 'credits': + require_once realpath(__DIR__ . '/../../includes/pages/guest_credits.php'); + $title = credits_title(); + $content = guest_credits(); + return [$title, $content]; + } + require_once realpath(__DIR__ . '/../../includes/pages/guest_start.php'); + $content = guest_start(); + return [$title, $content]; + } + + /** + * Render the template + * + * @param string $page + * @param string $title + * @param string $content + * @return Response + * @codeCoverageIgnore + */ + protected function renderPage($page, $title, $content) + { + global $user; + $event_config = EventConfig(); $parameters = [ 'key' => (isset($user) ? $user['api_key'] : ''), ]; diff --git a/src/Middleware/NotFoundResponse.php b/src/Middleware/NotFoundResponse.php index c5d51d2d..f9431c1d 100644 --- a/src/Middleware/NotFoundResponse.php +++ b/src/Middleware/NotFoundResponse.php @@ -2,6 +2,7 @@ namespace Engelsystem\Middleware; +use Engelsystem\Http\Response; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -22,12 +23,20 @@ class NotFoundResponse implements MiddlewareInterface ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface { + $info = _('This page could not be found or you don\'t have permission to view it. You probably have to sign in or register in order to gain access!'); + + return $this->renderPage($info); + } + + /** + * @param string $content + * @return Response + * @codeCoverageIgnore + */ + protected function renderPage($content) + { global $user; $event_config = EventConfig(); - $content = info( - _('This page could not be found or you don\'t have permission to view it. You probably have to sign in or register in order to gain access!'), - true - ); return response(view(__DIR__ . '/../../templates/layout.html', [ 'theme' => isset($user) ? $user['color'] : config('theme'), @@ -36,7 +45,7 @@ class NotFoundResponse implements MiddlewareInterface 'start_page_url' => page_link_to('/'), 'credits_url' => page_link_to('credits'), 'menu' => make_menu(), - 'content' => msg() . $content, + 'content' => msg() . info($content), 'header_toolbar' => header_toolbar(), 'faq_url' => config('faq_url'), 'contact_email' => config('contact_email'), diff --git a/src/Middleware/SendResponseHandler.php b/src/Middleware/SendResponseHandler.php index 06406fe0..34e70a87 100644 --- a/src/Middleware/SendResponseHandler.php +++ b/src/Middleware/SendResponseHandler.php @@ -24,8 +24,8 @@ class SendResponseHandler implements MiddlewareInterface ): ResponseInterface { $response = $handler->handle($request); - if (!headers_sent()) { - header(sprintf( + if (!$this->headersSent()) { + $this->sendHeader(sprintf( 'HTTP/%s %s %s', $response->getProtocolVersion(), $response->getStatusCode(), @@ -34,7 +34,7 @@ class SendResponseHandler implements MiddlewareInterface foreach ($response->getHeaders() as $name => $values) { foreach ($values as $value) { - header($name . ': ' . $value, false); + $this->sendHeader($name . ': ' . $value, false); } } } @@ -42,4 +42,28 @@ class SendResponseHandler implements MiddlewareInterface echo $response->getBody(); return $response; } + + /** + * Checks if headers have been sent + * + * @return bool + * @codeCoverageIgnore + */ + protected function headersSent() + { + return headers_sent(); + } + + /** + * Send a raw HTTP header + * + * @param string $content + * @param bool $replace + * @param int $code + * @codeCoverageIgnore + */ + protected function sendHeader($content, $replace = true, $code = null) + { + header($content, $replace, $code); + } } diff --git a/tests/Unit/Middleware/DispatcherTest.php b/tests/Unit/Middleware/DispatcherTest.php new file mode 100644 index 00000000..c01c5029 --- /dev/null +++ b/tests/Unit/Middleware/DispatcherTest.php @@ -0,0 +1,230 @@ +createMock(Application::class); + + $dispatcher = new Dispatcher([], $container); + $this->assertInstanceOf(MiddlewareInterface::class, $dispatcher); + $this->assertInstanceOf(RequestHandlerInterface::class, $dispatcher); + + $reflection = new Reflection(get_class($dispatcher)); + $property = $reflection->getProperty('container'); + $property->setAccessible(true); + $this->assertEquals($container, $property->getValue($dispatcher)); + } + + /** + * @covers \Engelsystem\Middleware\Dispatcher::process + */ + public function testProcess() + { + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->createMock(ServerRequestInterface::class); + /** @var ResponseInterface|MockObject $response */ + $response = $this->createMock(ResponseInterface::class); + /** @var RequestHandlerInterface|MockObject $handler */ + $handler = $this->createMock(RequestHandlerInterface::class); + + /** @var Dispatcher|MockObject $dispatcher */ + $dispatcher = $this->getMockBuilder(Dispatcher::class) + ->setMethods(['handle']) + ->getMock(); + + $dispatcher->expects($this->once()) + ->method('handle') + ->willReturn($response); + + $return = $dispatcher->process($request, $handler); + $this->assertEquals($response, $return); + + $reflection = new Reflection(get_class($dispatcher)); + $property = $reflection->getProperty('next'); + $property->setAccessible(true); + + $this->assertEquals($handler, $property->getValue($dispatcher)); + } + + /** + * @covers \Engelsystem\Middleware\Dispatcher::handle + */ + public function testHandle() + { + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->createMock(ServerRequestInterface::class); + /** @var ResponseInterface|MockObject $response */ + $response = $this->createMock(ResponseInterface::class); + /** @var MiddlewareInterface|MockObject $middleware */ + $middleware = $this->createMock(MiddlewareInterface::class); + + $dispatcher = new Dispatcher([$middleware]); + $middleware->expects($this->once()) + ->method('process') + ->with($request, $dispatcher) + ->willReturn($response); + + $return = $dispatcher->handle($request); + $this->assertEquals($response, $return); + } + + /** + * @covers \Engelsystem\Middleware\Dispatcher::handle + */ + public function testHandleNext() + { + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->createMock(ServerRequestInterface::class); + /** @var ResponseInterface|MockObject $response */ + $response = $this->createMock(ResponseInterface::class); + /** @var RequestHandlerInterface|MockObject $handler */ + $handler = $this->createMock(RequestHandlerInterface::class); + + $dispatcher = new Dispatcher(); + $handler->expects($this->once()) + ->method('handle') + ->with($request) + ->willReturn($response); + + $reflection = new Reflection(get_class($dispatcher)); + $property = $reflection->getProperty('next'); + $property->setAccessible(true); + $property->setValue($dispatcher, $handler); + + $return = $dispatcher->handle($request); + $this->assertEquals($response, $return); + } + + /** + * @covers \Engelsystem\Middleware\Dispatcher::handle + */ + public function testHandleNoMiddleware() + { + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->createMock(ServerRequestInterface::class); + + $this->expectException(LogicException::class); + + $dispatcher = new Dispatcher(); + $dispatcher->handle($request); + } + + /** + * @covers \Engelsystem\Middleware\Dispatcher::handle + */ + public function testHandleNoRealMiddleware() + { + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->createMock(ServerRequestInterface::class); + + $this->expectException(InvalidArgumentException::class); + + $dispatcher = new Dispatcher([new NotARealMiddleware()]); + $dispatcher->handle($request); + } + + /** + * @covers \Engelsystem\Middleware\Dispatcher::handle + */ + public function testHandleCallResolve() + { + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->createMock(ServerRequestInterface::class); + /** @var ResponseInterface|MockObject $response */ + $response = $this->createMock(ResponseInterface::class); + /** @var MiddlewareInterface|MockObject $middleware */ + $middleware = $this->createMock(MiddlewareInterface::class); + + /** @var Dispatcher|MockObject $dispatcher */ + $dispatcher = $this->getMockBuilder(Dispatcher::class) + ->setConstructorArgs([[MiddlewareInterface::class]]) + ->setMethods(['resolveMiddleware']) + ->getMock(); + + $dispatcher->expects($this->once()) + ->method('resolveMiddleware') + ->with(MiddlewareInterface::class) + ->willReturn($middleware); + + $middleware->expects($this->once()) + ->method('process') + ->with($request, $dispatcher) + ->willReturn($response); + + $return = $dispatcher->handle($request); + $this->assertEquals($response, $return); + } + + /** + * @covers \Engelsystem\Middleware\Dispatcher::resolveMiddleware + * @covers \Engelsystem\Middleware\Dispatcher::setContainer + */ + public function testResolveMiddleware() + { + /** @var Application|MockObject $container */ + $container = $this->createMock(Application::class); + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->createMock(ServerRequestInterface::class); + /** @var ResponseInterface|MockObject $response */ + $response = $this->createMock(ResponseInterface::class); + + $returnResponseMiddleware = new ReturnResponseMiddleware($response); + + $container->expects($this->exactly(2)) + ->method('has') + ->withConsecutive([ReturnResponseMiddleware::class], ['middleware']) + ->willReturnOnConsecutiveCalls(false, true); + + $container->expects($this->once()) + ->method('make') + ->with(ReturnResponseMiddleware::class) + ->willReturn($returnResponseMiddleware); + + $container->expects($this->once()) + ->method('get') + ->with('middleware') + ->willReturn($returnResponseMiddleware); + + $dispatcher = new Dispatcher([ReturnResponseMiddleware::class]); + $dispatcher->setContainer($container); + $dispatcher->handle($request); + + $dispatcher = new Dispatcher(['middleware'], $container); + $dispatcher->handle($request); + } + + /** + * @covers \Engelsystem\Middleware\Dispatcher::resolveMiddleware + */ + public function testResolveMiddlewareNoContainer() + { + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->createMock(ServerRequestInterface::class); + + $this->expectException(InvalidArgumentException::class); + + $dispatcher = new Dispatcher([ReturnResponseMiddleware::class]); + $dispatcher->handle($request); + } +} diff --git a/tests/Unit/Middleware/ExceptionHandlerTest.php b/tests/Unit/Middleware/ExceptionHandlerTest.php new file mode 100644 index 00000000..6d2a20e6 --- /dev/null +++ b/tests/Unit/Middleware/ExceptionHandlerTest.php @@ -0,0 +1,58 @@ +getMockForAbstractClass(ContainerInterface::class); + /** @var MockObject|ServerRequestInterface $request */ + $request = $this->getMockBuilder(ServerRequestInterface::class)->getMock(); + /** @var MockObject|ResponseInterface $response */ + $response = $this->getMockBuilder(Response::class)->getMock(); + /** @var MockObject|Handler $errorHandler */ + $errorHandler = $this->getMockBuilder(Handler::class)->getMock(); + $returnResponseHandler = new ReturnResponseMiddlewareHandler($response); + $throwExceptionHandler = new ExceptionMiddlewareHandler(); + + Application::setInstance($container); + + $container->expects($this->exactly(2)) + ->method('get') + ->withConsecutive(['error.handler'], ['psr7.response']) + ->willReturnOnConsecutiveCalls($errorHandler, $response); + + $response->expects($this->once()) + ->method('withContent') + ->willReturn($response); + $response->expects($this->once()) + ->method('withStatus') + ->with(500) + ->willReturn($response); + + $handler = new ExceptionHandler($container); + $return = $handler->process($request, $returnResponseHandler); + $this->assertEquals($response, $return); + + $return = $handler->process($request, $throwExceptionHandler); + $this->assertEquals($response, $return); + } +} diff --git a/tests/Unit/Middleware/LegacyMiddlewareTest.php b/tests/Unit/Middleware/LegacyMiddlewareTest.php new file mode 100644 index 00000000..34e60b60 --- /dev/null +++ b/tests/Unit/Middleware/LegacyMiddlewareTest.php @@ -0,0 +1,85 @@ +getMockForAbstractClass(ContainerInterface::class); + /** @var LegacyMiddleware|MockObject $middleware */ + $middleware = $this->getMockBuilder(LegacyMiddleware::class) + ->setConstructorArgs([$container]) + ->setMethods(['loadPage', 'renderPage']) + ->getMock(); + /** @var Request|MockObject $defaultRequest */ + $defaultRequest = $this->createMock(Request::class); + /** @var ParameterBag|MockObject $parameters */ + $parameters = $this->createMock(ParameterBag::class); + /** @var ResponseInterface|MockObject $response */ + $response = $this->getMockForAbstractClass(ResponseInterface::class); + /** @var RequestHandlerInterface|MockObject $handler */ + $handler = $this->getMockForAbstractClass(RequestHandlerInterface::class); + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->getMockForAbstractClass(ServerRequestInterface::class); + + $middleware->expects($this->exactly(2)) + ->method('loadPage') + ->withConsecutive(['user_worklog'], ['login']) + ->willReturnOnConsecutiveCalls( + ['title', 'content'], + ['title2', 'content2'] + ); + + $middleware->expects($this->exactly(2)) + ->method('renderPage') + ->withConsecutive( + ['user_worklog', 'title', 'content'], + ['login', 'title2', 'content2'] + ) + ->willReturn($response); + + $container->expects($this->atLeastOnce()) + ->method('get') + ->with('request') + ->willReturn($defaultRequest); + + $defaultRequest->query = $parameters; + $defaultRequest->expects($this->once()) + ->method('path') + ->willReturn('user-worklog'); + + $parameters->expects($this->exactly(3)) + ->method('get') + ->with('p') + ->willReturnOnConsecutiveCalls( + null, + 'foo', + '/' + ); + + $handler->expects($this->once()) + ->method('handle') + ->with($request) + ->willReturn($response); + + $middleware->process($request, $handler); + $middleware->process($request, $handler); + $middleware->process($request, $handler); + } +} diff --git a/tests/Unit/Middleware/NotFoundResponseTest.php b/tests/Unit/Middleware/NotFoundResponseTest.php new file mode 100644 index 00000000..9279e81d --- /dev/null +++ b/tests/Unit/Middleware/NotFoundResponseTest.php @@ -0,0 +1,39 @@ +getMockBuilder(NotFoundResponse::class) + ->setMethods(['renderPage']) + ->getMock(); + /** @var ResponseInterface|MockObject $response */ + $response = $this->getMockForAbstractClass(ResponseInterface::class); + /** @var RequestHandlerInterface|MockObject $handler */ + $handler = $this->getMockForAbstractClass(RequestHandlerInterface::class); + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->getMockForAbstractClass(ServerRequestInterface::class); + + $middleware->expects($this->once()) + ->method('renderPage') + ->willReturn($response); + + $handler->expects($this->never()) + ->method('handle'); + + $middleware->process($request, $handler); + } +} diff --git a/tests/Unit/Middleware/SendResponseHandlerTest.php b/tests/Unit/Middleware/SendResponseHandlerTest.php new file mode 100644 index 00000000..7431299e --- /dev/null +++ b/tests/Unit/Middleware/SendResponseHandlerTest.php @@ -0,0 +1,71 @@ +getMockBuilder(SendResponseHandler::class) + ->setMethods(['headersSent', 'sendHeader']) + ->getMock(); + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->getMockForAbstractClass(ServerRequestInterface::class); + /** @var ResponseInterface|MockObject $response */ + $response = $this->getMockForAbstractClass(ResponseInterface::class); + /** @var RequestHandlerInterface|MockObject $handler */ + $handler = $this->getMockForAbstractClass(RequestHandlerInterface::class); + + $middleware->expects($this->atLeastOnce()) + ->method('headersSent') + ->willReturnOnConsecutiveCalls(true, false); + + $middleware->expects($this->exactly(4)) + ->method('sendHeader') + ->withConsecutive( + ['HTTP/0.7 505 Something went wrong!', true, 505], + ['Foo: bar', false], + ['lorem: ipsum', false], + ['lorem: dolor', false] + ); + + $handler->expects($this->exactly(2)) + ->method('handle') + ->with($request) + ->willReturn($response); + + $response->expects($this->exactly(2)) + ->method('getBody') + ->willReturn('Lorem Ipsum!'); + + $response->expects($this->atLeastOnce()) + ->method('getProtocolVersion') + ->willReturn('0.7'); + + $response->expects($this->atLeastOnce()) + ->method('getStatusCode') + ->willReturn(505); + + $response->expects($this->once()) + ->method('getReasonPhrase') + ->willReturn('Something went wrong!'); + $response->expects($this->once()) + ->method('getHeaders') + ->willReturn(['Foo' => ['bar'], 'lorem' => ['ipsum', 'dolor']]); + + $this->expectOutputString('Lorem Ipsum!Lorem Ipsum!'); + $middleware->process($request, $handler); + $middleware->process($request, $handler); + } +} diff --git a/tests/Unit/Middleware/Stub/ExceptionMiddlewareHandler.php b/tests/Unit/Middleware/Stub/ExceptionMiddlewareHandler.php new file mode 100644 index 00000000..5e374bea --- /dev/null +++ b/tests/Unit/Middleware/Stub/ExceptionMiddlewareHandler.php @@ -0,0 +1,23 @@ +response = $response; + } + + /** + * Process an incoming server request and return a response, optionally delegating + * response creation to a handler. + * + * Could be used to group middleware + * + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + */ + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + return $this->response; + } +} diff --git a/tests/Unit/Middleware/Stub/ReturnResponseMiddlewareHandler.php b/tests/Unit/Middleware/Stub/ReturnResponseMiddlewareHandler.php new file mode 100644 index 00000000..323e07b4 --- /dev/null +++ b/tests/Unit/Middleware/Stub/ReturnResponseMiddlewareHandler.php @@ -0,0 +1,30 @@ +response = $response; + } + + /** + * Returns a given response + * + * @param ServerRequestInterface $request + * @return ResponseInterface + * @throws \Exception + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->response; + } +} -- cgit v1.2.3-54-g00ecf