Merge branch 'MyIgel-rebuild-psr7'

This commit is contained in:
msquare 2018-09-04 18:24:11 +02:00
commit b320fc7790
46 changed files with 2786 additions and 278 deletions

View File

@ -15,13 +15,20 @@
], ],
"require": { "require": {
"php": ">=7.0.0", "php": ">=7.0.0",
"ext-gettext": "*",
"ext-json": "*",
"ext-PDO": "*",
"erusev/parsedown": "^1.6", "erusev/parsedown": "^1.6",
"illuminate/container": "5.5.*", "illuminate/container": "5.5.*",
"illuminate/database": "5.5.*", "illuminate/database": "5.5.*",
"illuminate/support": "^5.5", "illuminate/support": "^5.5",
"psr/container": "^1.0", "psr/container": "^1.0",
"psr/http-server-middleware": "^1.0",
"psr/log": "^1.0", "psr/log": "^1.0",
"symfony/http-foundation": "^3.3" "symfony/http-foundation": "^3.3",
"symfony/psr-http-message-bridge": "^1.0",
"twbs/bootstrap": "^3.3",
"zendframework/zend-diactoros": "^1.7"
}, },
"require-dev": { "require-dev": {
"filp/whoops": "^2.1", "filp/whoops": "^2.1",

View File

@ -13,5 +13,15 @@ return [
\Engelsystem\Database\DatabaseServiceProvider::class, \Engelsystem\Database\DatabaseServiceProvider::class,
\Engelsystem\Http\RequestServiceProvider::class, \Engelsystem\Http\RequestServiceProvider::class,
\Engelsystem\Http\SessionServiceProvider::class, \Engelsystem\Http\SessionServiceProvider::class,
\Engelsystem\Http\ResponseServiceProvider::class,
\Engelsystem\Http\Psr7ServiceProvider::class,
],
// Application middleware
'middleware' => [
\Engelsystem\Middleware\SendResponseHandler::class,
\Engelsystem\Middleware\ExceptionHandler::class,
\Engelsystem\Middleware\LegacyMiddleware::class,
\Engelsystem\Middleware\NotFoundResponse::class,
], ],
]; ];

View File

@ -1,6 +1,6 @@
FROM composer AS composer FROM composer AS composer
COPY composer.json /app/ COPY composer.json /app/
RUN composer --no-ansi install --no-dev RUN composer --no-ansi install --no-dev --ignore-platform-reqs
RUN composer --no-ansi dump-autoload --optimize RUN composer --no-ansi dump-autoload --optimize
FROM node:8-alpine as themes FROM node:8-alpine as themes

View File

@ -73,8 +73,6 @@ function success($msg, $immediately = false)
*/ */
function alert($class, $msg, $immediately = false) function alert($class, $msg, $immediately = false)
{ {
$session = session();
if (empty($msg)) { if (empty($msg)) {
return ''; return '';
} }
@ -83,6 +81,7 @@ function alert($class, $msg, $immediately = false)
return '<div class="alert alert-' . $class . '">' . $msg . '</div>'; return '<div class="alert alert-' . $class . '">' . $msg . '</div>';
} }
$session = session();
$message = $session->get('msg', ''); $message = $session->get('msg', '');
$message .= alert($class, $msg, true); $message .= alert($class, $msg, true);
$session->set('msg', $message); $session->set('msg', $message);

View File

@ -2876,13 +2876,13 @@ msgstr ""
"keine Nummer hast, bitte einfach \"-\" angeben." "keine Nummer hast, bitte einfach \"-\" angeben."
#: /Users/msquare/workspace/projects/engelsystem/public/index.php:218 #: /Users/msquare/workspace/projects/engelsystem/public/index.php:218
msgid "No Access" msgid "Page not found"
msgstr "Kein Zugriff" msgstr "Seite nicht gefunden"
#: /Users/msquare/workspace/projects/engelsystem/public/index.php:219 #: /Users/msquare/workspace/projects/engelsystem/public/index.php:219
msgid "" msgid ""
"You don't have permission to view this page . You probably have to sign in " "This page could not be found or you don't have permission to view it. You "
"or register in order to gain access!" "probably have to sign in or register in order to gain access!"
msgstr "" msgstr ""
"Du hast keinen Zugriff auf diese Seite. Registriere Dich und logge Dich " "Diese Seite existiert nicht oder Du hast keinen Zugriff. Melde Dich an um Zugriff "
"bitte ein, um Zugriff zu erhalten!" "zu erhalten!"

View File

@ -1,247 +1,19 @@
<?php <?php
use Engelsystem\Http\Request; use Engelsystem\Application;
use Engelsystem\Middleware\Dispatcher;
use Psr\Http\Message\ServerRequestInterface;
require_once realpath(__DIR__ . '/../includes/engelsystem.php'); require_once realpath(__DIR__ . '/../includes/engelsystem.php');
$free_pages = [ /** @var Application $app */
'admin_event_config', $app = app();
'angeltypes',
'api',
'atom',
'credits',
'ical',
'login',
'public_dashboard',
'rooms',
'shift_entries',
'shifts',
'shifts_json_export',
'stats',
'users',
'user_driver_licenses',
'user_password_recovery',
'user_worklog'
];
// Gewünschte Seite/Funktion /** @var ServerRequestInterface $request */
$page = ''; $request = $app->get('psr7.request');
$title = ''; $middleware = $app->getMiddleware();
$content = '';
/** @var Request $request */ $dispatcher = new Dispatcher($middleware);
$request = $app->get('request'); $dispatcher->setContainer($app);
$page = $request->query->get('p');
if (empty($page)) {
$page = $request->path();
$page = str_replace('-', '_', $page);
}
if ($page == '/') {
$page = isset($user) ? 'news' : 'login';
}
if ( $dispatcher->handle($request);
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 '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')
? ' <link href="'
. page_link_to('atom', $parameters)
. '" type = "application/atom+xml" rel = "alternate" title = "Atom Feed">'
: '',
'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_short(),
'event_info' => EventConfig_info($event_config) . ' <br />'
]);

View File

@ -7,6 +7,7 @@ use Engelsystem\Container\Container;
use Engelsystem\Container\ServiceProvider; use Engelsystem\Container\ServiceProvider;
use Illuminate\Container\Container as IlluminateContainer; use Illuminate\Container\Container as IlluminateContainer;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\Http\Server\MiddlewareInterface;
class Application extends Container class Application extends Container
{ {
@ -16,6 +17,9 @@ class Application extends Container
/** @var bool */ /** @var bool */
protected $isBootstrapped = false; protected $isBootstrapped = false;
/** @var MiddlewareInterface[]|string[] */
protected $middleware;
/** /**
* Registered service providers * Registered service providers
* *
@ -85,6 +89,8 @@ class Application extends Container
foreach ($config->get('providers', []) as $provider) { foreach ($config->get('providers', []) as $provider) {
$this->register($provider); $this->register($provider);
} }
$this->middleware = $config->get('middleware', []);
} }
foreach ($this->serviceProviders as $provider) { foreach ($this->serviceProviders as $provider) {
@ -136,4 +142,12 @@ class Application extends Container
{ {
return $this->isBootstrapped; return $this->isBootstrapped;
} }
/**
* @return MiddlewareInterface[]|string[]
*/
public function getMiddleware()
{
return $this->middleware;
}
} }

View File

@ -54,8 +54,10 @@ class Handler
/** /**
* @param Throwable $e * @param Throwable $e
* @param bool $return
* @return string
*/ */
public function exceptionHandler($e) public function exceptionHandler($e, $return = false)
{ {
if (!$this->request instanceof Request) { if (!$this->request instanceof Request) {
$this->request = new Request(); $this->request = new Request();
@ -63,8 +65,21 @@ class Handler
$handler = $this->handler[$this->environment]; $handler = $this->handler[$this->environment];
$handler->report($e); $handler->report($e);
ob_start();
$handler->render($this->request, $e); $handler->render($this->request, $e);
if ($return) {
$output = ob_get_contents();
ob_end_clean();
return $output;
}
http_response_code(500);
ob_end_flush();
$this->terminateApplicationImmediately(); $this->terminateApplicationImmediately();
return '';
} }
/** /**

View File

@ -34,6 +34,8 @@ class Whoops extends Legacy implements HandlerInterface
$whoops = $this->app->make(WhoopsRunner::class); $whoops = $this->app->make(WhoopsRunner::class);
$handler = $this->getPrettyPageHandler($e); $handler = $this->getPrettyPageHandler($e);
$whoops->pushHandler($handler); $whoops->pushHandler($handler);
$whoops->writeToOutput(false);
$whoops->allowQuit(false);
if ($request->isXmlHttpRequest()) { if ($request->isXmlHttpRequest()) {
$handler = $this->getJsonResponseHandler(); $handler = $this->getJsonResponseHandler();

248
src/Http/MessageTrait.php Normal file
View File

@ -0,0 +1,248 @@
<?php
namespace Engelsystem\Http;
use Psr\Http\Message\StreamInterface;
use Zend\Diactoros\Stream;
/**
* @implements \Psr\Http\Message\MessageInterface
* @extends \Symfony\Component\HttpFoundation\Response
*/
trait MessageTrait
{
/**
* Retrieves the HTTP protocol version as a string.
*
* The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
*
* @return string HTTP protocol version.
*/
public function getProtocolVersion()
{
return parent::getProtocolVersion();
}
/**
* Return an instance with the specified HTTP protocol version.
*
* The version string MUST contain only the HTTP version number (e.g.,
* "1.1", "1.0").
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* new protocol version.
*
* @param string $version HTTP protocol version
* @return static
*/
public function withProtocolVersion($version)
{
$new = clone $this;
if (method_exists($new, 'setProtocolVersion')) {
$new->setProtocolVersion($version);
} else {
$new->server->set('SERVER_PROTOCOL', $version);
}
return $new;
}
/**
* Retrieves all message header values.
*
* The keys represent the header name as it will be sent over the wire, and
* each value is an array of strings associated with the header.
*
* // Represent the headers as a string
* foreach ($message->getHeaders() as $name => $values) {
* echo $name . ": " . implode(", ", $values);
* }
*
* // Emit headers iteratively:
* foreach ($message->getHeaders() as $name => $values) {
* foreach ($values as $value) {
* header(sprintf('%s: %s', $name, $value), false);
* }
* }
*
* While header names are not case-sensitive, getHeaders() will preserve the
* exact case in which headers were originally specified.
*
* @return string[][] Returns an associative array of the message's headers. Each
* key MUST be a header name, and each value MUST be an array of strings
* for that header.
*/
public function getHeaders()
{
if (method_exists($this->headers, 'allPreserveCase')) {
return $this->headers->allPreserveCase();
}
return $this->headers->all();
}
/**
* Checks if a header exists by the given case-insensitive name.
*
* @param string $name Case-insensitive header field name.
* @return bool Returns true if any header names match the given header
* name using a case-insensitive string comparison. Returns false if
* no matching header name is found in the message.
*/
public function hasHeader($name)
{
return $this->headers->has($name);
}
/**
* Retrieves a message header value by the given case-insensitive name.
*
* This method returns an array of all the header values of the given
* case-insensitive header name.
*
* If the header does not appear in the message, this method MUST return an
* empty array.
*
* @param string $name Case-insensitive header field name.
* @return string[] An array of string values as provided for the given
* header. If the header does not appear in the message, this method MUST
* return an empty array.
*/
public function getHeader($name)
{
return $this->headers->get($name, null, false);
}
/**
* Retrieves a comma-separated string of the values for a single header.
*
* This method returns all of the header values of the given
* case-insensitive header name as a string concatenated together using
* a comma.
*
* NOTE: Not all header values may be appropriately represented using
* comma concatenation. For such headers, use getHeader() instead
* and supply your own delimiter when concatenating.
*
* If the header does not appear in the message, this method MUST return
* an empty string.
*
* @param string $name Case-insensitive header field name.
* @return string A string of values as provided for the given header
* concatenated together using a comma. If the header does not appear in
* the message, this method MUST return an empty string.
*/
public function getHeaderLine($name)
{
return implode(',', $this->getHeader($name));
}
/**
* Return an instance with the provided value replacing the specified header.
*
* While header names are case-insensitive, the casing of the header will
* be preserved by this function, and returned from getHeaders().
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* new and/or updated header and value.
*
* @param string $name Case-insensitive header field name.
* @param string|string[] $value Header value(s).
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
public function withHeader($name, $value)
{
$new = clone $this;
$new->headers->set($name, $value);
return $new;
}
/**
* Return an instance with the specified header appended with the given value.
*
* Existing values for the specified header will be maintained. The new
* value(s) will be appended to the existing list. If the header did not
* exist previously, it will be added.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* new header and/or value.
*
* @param string $name Case-insensitive header field name to add.
* @param string|string[] $value Header value(s).
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
public function withAddedHeader($name, $value)
{
$new = clone $this;
$new->headers->set($name, $value, false);
return $new;
}
/**
* Return an instance without the specified header.
*
* Header resolution MUST be done without case-sensitivity.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that removes
* the named header.
*
* @param string $name Case-insensitive header field name to remove.
* @return static
*/
public function withoutHeader($name)
{
$new = clone $this;
$new->headers->remove($name);
return $new;
}
/**
* Gets the body of the message.
*
* @return StreamInterface Returns the body as a stream.
*/
public function getBody()
{
$stream = new Stream('php://memory', 'wb+');
$stream->write($this->getContent());
$stream->rewind();
return $stream;
}
/**
* Return an instance with the specified message body.
*
* The body MUST be a StreamInterface object.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return a new instance that has the
* new body stream.
*
* @param StreamInterface $body Body.
* @return static
* @throws \InvalidArgumentException When the body is not valid.
*/
public function withBody(StreamInterface $body)
{
$new = clone $this;
if (method_exists($new, 'setContent')) {
$new->setContent($body);
} else {
$new->content = $body;
}
return $new;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Engelsystem\Http;
use Engelsystem\Container\ServiceProvider;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
class Psr7ServiceProvider extends ServiceProvider
{
public function register()
{
/** @var DiactorosFactory $psr7Factory */
$psr7Factory = $this->app->make(DiactorosFactory::class);
$this->app->instance('psr7.factory', $psr7Factory);
/** @var Request $request */
$request = $this->app->get('request');
$this->app->instance('psr7.request', $request);
$this->app->bind(ServerRequestInterface::class, 'psr7.request');
/** @var Response $response */
$response = $this->app->get('response');
$this->app->instance('psr7.response', $response);
$this->app->bind(ResponseInterface::class, 'psr7.response');
}
}

View File

@ -2,10 +2,18 @@
namespace Engelsystem\Http; namespace Engelsystem\Http;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyFile;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest; use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
use Zend\Diactoros\UploadedFile;
use Zend\Diactoros\Uri;
class Request extends SymfonyRequest class Request extends SymfonyRequest implements ServerRequestInterface
{ {
use MessageTrait;
/** /**
* Get POST input * Get POST input
* *
@ -64,4 +72,441 @@ class Request extends SymfonyRequest
{ {
return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/'); return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/');
} }
/**
* Retrieves the message's request target.
*
*
* Retrieves the message's request-target either as it will appear (for
* clients), as it appeared at request (for servers), or as it was
* specified for the instance (see withRequestTarget()).
*
* In most cases, this will be the origin-form of the composed URI,
* unless a value was provided to the concrete implementation (see
* withRequestTarget() below).
*
* If no URI is available, and no request-target has been specifically
* provided, this method MUST return the string "/".
*
* @return string
*/
public function getRequestTarget()
{
$query = $this->getQueryString();
return '/' . $this->path() . (!empty($query) ? '?' . $query : '');
}
/**
* Return an instance with the specific request-target.
*
* If the request needs a non-origin-form request-target e.g., for
* specifying an absolute-form, authority-form, or asterisk-form
* this method may be used to create an instance with the specified
* request-target, verbatim.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* changed request target.
*
* @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
* request-target forms allowed in request messages)
* @param mixed $requestTarget
* @return static
*/
public function withRequestTarget($requestTarget)
{
return $this->create($requestTarget);
}
/**
* Return an instance with the provided HTTP method.
*
* While HTTP method names are typically all uppercase characters, HTTP
* method names are case-sensitive and thus implementations SHOULD NOT
* modify the given string.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* changed request method.
*
* @param string $method Case-sensitive method.
* @return static
* @throws \InvalidArgumentException for invalid HTTP methods.
*/
public function withMethod($method)
{
$new = clone $this;
$new->setMethod($method);
return $new;
}
/**
* Returns an instance with the provided URI.
*
* This method MUST update the Host header of the returned request by
* default if the URI contains a host component. If the URI does not
* contain a host component, any pre-existing Host header MUST be carried
* over to the returned request.
*
* You can opt-in to preserving the original state of the Host header by
* setting `$preserveHost` to `true`. When `$preserveHost` is set to
* `true`, this method interacts with the Host header in the following ways:
*
* - If the Host header is missing or empty, and the new URI contains
* a host component, this method MUST update the Host header in the returned
* request.
* - If the Host header is missing or empty, and the new URI does not contain a
* host component, this method MUST NOT update the Host header in the returned
* request.
* - If a Host header is present and non-empty, this method MUST NOT update
* the Host header in the returned request.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* new UriInterface instance.
*
* @link http://tools.ietf.org/html/rfc3986#section-4.3
* @param UriInterface $uri New request URI to use.
* @param bool $preserveHost Preserve the original state of the Host header.
* @return static
*/
public function withUri(UriInterface $uri, $preserveHost = false)
{
$new = $this->create($uri);
if ($preserveHost) {
$new->headers->set('HOST', $this->getHost());
}
return $new;
}
/**
* Retrieves the URI instance.
*
* This method MUST return a UriInterface instance.
*
* @link http://tools.ietf.org/html/rfc3986#section-4.3
* @return string|UriInterface Returns a UriInterface instance
* representing the URI of the request.
*/
public function getUri()
{
$uri = parent::getUri();
return new Uri($uri);
}
/**
* Retrieve server parameters.
*
* Retrieves data related to the incoming request environment,
* typically derived from PHP's $_SERVER superglobal. The data IS NOT
* REQUIRED to originate from $_SERVER.
*
* @return array
*/
public function getServerParams()
{
return $this->server->all();
}
/**
* Retrieve cookies.
*
* Retrieves cookies sent by the client to the server.
*
* The data MUST be compatible with the structure of the $_COOKIE
* superglobal.
*
* @return array
*/
public function getCookieParams()
{
return $this->cookies->all();
}
/**
* Return an instance with the specified cookies.
*
* The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
* be compatible with the structure of $_COOKIE. Typically, this data will
* be injected at instantiation.
*
* This method MUST NOT update the related Cookie header of the request
* instance, nor related values in the server params.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated cookie values.
*
* @param array $cookies Array of key/value pairs representing cookies.
* @return static
*/
public function withCookieParams(array $cookies)
{
$new = clone $this;
$new->cookies = clone $this->cookies;
$new->cookies->replace($cookies);
return $new;
}
/**
* Retrieve query string arguments.
*
* Retrieves the deserialized query string arguments, if any.
*
* Note: the query params might not be in sync with the URI or server
* params. If you need to ensure you are only getting the original
* values, you may need to parse the query string from `getUri()->getQuery()`
* or from the `QUERY_STRING` server param.
*
* @return array
*/
public function getQueryParams()
{
return $this->query->all();
}
/**
* Return an instance with the specified query string arguments.
*
* These values SHOULD remain immutable over the course of the incoming
* request. They MAY be injected during instantiation, such as from PHP's
* $_GET superglobal, or MAY be derived from some other value such as the
* URI. In cases where the arguments are parsed from the URI, the data
* MUST be compatible with what PHP's parse_str() would return for
* purposes of how duplicate query parameters are handled, and how nested
* sets are handled.
*
* Setting query string arguments MUST NOT change the URI stored by the
* request, nor the values in the server params.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated query string arguments.
*
* @param array $query Array of query string arguments, typically from
* $_GET.
* @return static
*/
public function withQueryParams(array $query)
{
$new = clone $this;
$new->query = clone $this->query;
$new->query->replace($query);
return $new;
}
/**
* Retrieve normalized file upload data.
*
* This method returns upload metadata in a normalized tree, with each leaf
* an instance of Psr\Http\Message\UploadedFileInterface.
*
* These values MAY be prepared from $_FILES or the message body during
* instantiation, or MAY be injected via withUploadedFiles().
*
* @return array An array tree of UploadedFileInterface instances; an empty
* array MUST be returned if no data is present.
*/
public function getUploadedFiles()
{
$files = [];
foreach ($this->files as $file) {
/** @var SymfonyFile $file */
$files[] = new UploadedFile(
$file->getPath(),
$file->getSize(),
$file->getError(),
$file->getClientOriginalName(),
$file->getClientMimeType()
);
}
return $files;
}
/**
* Create a new instance with the specified uploaded files.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated body parameters.
*
* @param array $uploadedFiles An array tree of UploadedFileInterface instances.
* @return static
* @throws \InvalidArgumentException if an invalid structure is provided.
*/
public function withUploadedFiles(array $uploadedFiles)
{
$new = clone $this;
$new->files = clone $this->files;
$files = [];
foreach ($uploadedFiles as $file) {
/** @var UploadedFileInterface $file */
$filename = tempnam(sys_get_temp_dir(), 'upload');
$handle = fopen($filename, "w");
fwrite($handle, $file->getStream()->getContents());
fclose($handle);
$files[] = new SymfonyFile(
$filename,
$file->getClientFilename(),
$file->getClientMediaType(),
$file->getSize(),
$file->getError()
);
}
$new->files->add($files);
return $new;
}
/**
* Retrieve any parameters provided in the request body.
*
* If the request Content-Type is either application/x-www-form-urlencoded
* or multipart/form-data, and the request method is POST, this method MUST
* return the contents of $_POST.
*
* Otherwise, this method may return any results of deserializing
* the request body content; as parsing returns structured content, the
* potential types MUST be arrays or objects only. A null value indicates
* the absence of body content.
*
* @return null|array|object The deserialized body parameters, if any.
* These will typically be an array or object.
*/
public function getParsedBody()
{
return $this->request->all();
}
/**
* Return an instance with the specified body parameters.
*
* These MAY be injected during instantiation.
*
* If the request Content-Type is either application/x-www-form-urlencoded
* or multipart/form-data, and the request method is POST, use this method
* ONLY to inject the contents of $_POST.
*
* The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
* deserializing the request body content. Deserialization/parsing returns
* structured data, and, as such, this method ONLY accepts arrays or objects,
* or a null value if nothing was available to parse.
*
* As an example, if content negotiation determines that the request data
* is a JSON payload, this method could be used to create a request
* instance with the deserialized parameters.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated body parameters.
*
* @param null|array|object $data The deserialized body data. This will
* typically be in an array or object.
* @return static
* @throws \InvalidArgumentException if an unsupported argument type is
* provided.
*/
public function withParsedBody($data)
{
$new = clone $this;
$new->request = clone $this->request;
$new->request->replace($data);
return $new;
}
/**
* Retrieve attributes derived from the request.
*
* The request "attributes" may be used to allow injection of any
* parameters derived from the request: e.g., the results of path
* match operations; the results of decrypting cookies; the results of
* deserializing non-form-encoded message bodies; etc. Attributes
* will be application and request specific, and CAN be mutable.
*
* @return array Attributes derived from the request.
*/
public function getAttributes()
{
return $this->attributes->all();
}
/**
* Retrieve a single derived request attribute.
*
* Retrieves a single derived request attribute as described in
* getAttributes(). If the attribute has not been previously set, returns
* the default value as provided.
*
* This method obviates the need for a hasAttribute() method, as it allows
* specifying a default value to return if the attribute is not found.
*
* @see getAttributes()
* @param string $name The attribute name.
* @param mixed $default Default value to return if the attribute does not exist.
* @return mixed
*/
public function getAttribute($name, $default = null)
{
return $this->attributes->get($name, $default);
}
/**
* Return an instance with the specified derived request attribute.
*
* This method allows setting a single derived request attribute as
* described in getAttributes().
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated attribute.
*
* @see getAttributes()
* @param string $name The attribute name.
* @param mixed $value The value of the attribute.
* @return static
*/
public function withAttribute($name, $value)
{
$new = clone $this;
$new->attributes = clone $this->attributes;
$new->attributes->set($name, $value);
return $new;
}
/**
* Return an instance that removes the specified derived request attribute.
*
* This method allows removing a single derived request attribute as
* described in getAttributes().
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that removes
* the attribute.
*
* @see getAttributes()
* @param string $name The attribute name.
* @return static
*/
public function withoutAttribute($name)
{
$new = clone $this;
$new->attributes = clone $this->attributes;
$new->attributes->remove($name);
return $new;
}
} }

75
src/Http/Response.php Normal file
View File

@ -0,0 +1,75 @@
<?php
namespace Engelsystem\Http;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
class Response extends SymfonyResponse implements ResponseInterface
{
use MessageTrait;
/**
* Return an instance with the specified status code and, optionally, reason phrase.
*
* If no reason phrase is specified, implementations MAY choose to default
* to the RFC 7231 or IANA recommended reason phrase for the response's
* status code.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated status and reason phrase.
*
* @link http://tools.ietf.org/html/rfc7231#section-6
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
* @param int $code The 3-digit integer result code to set.
* @param string $reasonPhrase The reason phrase to use with the
* provided status code; if none is provided, implementations MAY
* use the defaults as suggested in the HTTP specification.
* @return static
* @throws \InvalidArgumentException For invalid status code arguments.
*/
public function withStatus($code, $reasonPhrase = '')
{
$new = clone $this;
$new->setStatusCode($code, !empty($reasonPhrase) ? $reasonPhrase : null);
return $new;
}
/**
* Gets the response reason phrase associated with the status code.
*
* Because a reason phrase is not a required element in a response
* status line, the reason phrase value MAY be null. Implementations MAY
* choose to return the default RFC 7231 recommended reason phrase (or those
* listed in the IANA HTTP Status Code Registry) for the response's
* status code.
*
* @link http://tools.ietf.org/html/rfc7231#section-6
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
* @return string Reason phrase; must return an empty string if none present.
*/
public function getReasonPhrase()
{
return $this->statusText;
}
/**
* Return an instance with the specified content.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated status and reason phrase.
*
* @param mixed $content Content that can be cast to string
* @return static
*/
public function withContent($content)
{
$new = clone $this;
$new->setContent($content);
return $new;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Engelsystem\Http;
use Engelsystem\Container\ServiceProvider;
class ResponseServiceProvider extends ServiceProvider
{
public function register()
{
$response = $this->app->make(Response::class);
$this->app->instance('response', $response);
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Application;
use InvalidArgumentException;
use LogicException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class Dispatcher implements MiddlewareInterface, RequestHandlerInterface
{
/** @var MiddlewareInterface[]|string[] */
protected $stack;
/** @var Application */
protected $container;
/** @var RequestHandlerInterface */
protected $next;
/**
* @param MiddlewareInterface[]|string[] $stack
* @param Application|null $container
*/
public function __construct($stack = [], Application $container = null)
{
$this->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;
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Exceptions\Handler as ExceptionsHandler;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ExceptionHandler implements MiddlewareInterface
{
/** @var ContainerInterface */
protected $container;
/**
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->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);
}
}
}

View File

@ -0,0 +1,296 @@
<?php
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;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class LegacyMiddleware implements MiddlewareInterface
{
protected $free_pages = [
'admin_event_config',
'angeltypes',
'api',
'atom',
'credits',
'ical',
'login',
'public_dashboard',
'rooms',
'shift_entries',
'shifts',
'shifts_json_export',
'shifts_json_export_all',
'stats',
'users',
'user_driver_licenses',
'user_password_recovery',
'user_worklog'
];
/** @var ContainerInterface */
protected $container;
/**
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->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');
$page = $appRequest->query->get('p');
if (empty($page)) {
$page = $appRequest->path();
$page = str_replace('-', '_', $page);
}
if ($page == '/') {
$page = isset($user) ? 'news' : 'login';
}
$title = $content = '';
if (
preg_match('~^\w+$~i', $page)
&& (
in_array($page, $this->free_pages)
|| (isset($privileges) && in_array($page, $privileges))
)
) {
list($title, $content) = $this->loadPage($page);
}
if (empty($title) and empty($content)) {
return $handler->handle($request);
}
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'] : ''),
];
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')
? ' <link href="'
. page_link_to('atom', $parameters)
. '" type = "application/atom+xml" rel = "alternate" title = "Atom Feed">'
: '',
'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) . ' <br />'
]));
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Http\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class NotFoundResponse implements MiddlewareInterface
{
/**
* Returns a 404: Page not found response
*
* Should be the last middleware
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(
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();
return response(view(__DIR__ . '/../../templates/layout.html', [
'theme' => 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() . info($content),
'header_toolbar' => header_toolbar(),
'faq_url' => config('faq_url'),
'contact_email' => config('contact_email'),
'locale' => locale(),
'event_info' => EventConfig_info($event_config) . ' <br />'
]), 404);
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Engelsystem\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class SendResponseHandler implements MiddlewareInterface
{
/**
* Send the server response to the client
*
* This should be the first middleware
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$response = $handler->handle($request);
if (!$this->headersSent()) {
$this->sendHeader(sprintf(
'HTTP/%s %s %s',
$response->getProtocolVersion(),
$response->getStatusCode(),
$response->getReasonPhrase()
), true, $response->getStatusCode());
foreach ($response->getHeaders() as $name => $values) {
foreach ($values as $value) {
$this->sendHeader($name . ': ' . $value, false);
}
}
}
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);
}
}

View File

@ -4,6 +4,7 @@
use Engelsystem\Application; use Engelsystem\Application;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Renderer\Renderer; use Engelsystem\Renderer\Renderer;
use Engelsystem\Routing\UrlGeneratorInterface; use Engelsystem\Routing\UrlGeneratorInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpFoundation\Session\SessionInterface;
@ -12,7 +13,7 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
* Get the global app instance * Get the global app instance
* *
* @param string $id * @param string $id
* @return mixed * @return mixed|Application
*/ */
function app($instance_id = null) function app($instance_id = null)
{ {
@ -80,6 +81,27 @@ function request($key = null, $default = null)
return $request->input($key, $default); return $request->input($key, $default);
} }
/**
* @param string $content
* @param int $status
* @param array $headers
* @return Response
*/
function response($content = '', $status = 200, $headers = [])
{
/** @var Response $response */
$response = app('psr7.response');
$response = $response
->withContent($content)
->withStatus($status);
foreach ($headers as $key => $value) {
$response = $response->withAddedHeader($key, $value);
}
return $response;
}
/** /**
* @param string $key * @param string $key
* @param mixed $default * @param mixed $default

View File

@ -0,0 +1,14 @@
<?php
namespace Engelsystem\Test\Feature;
use PHPUnit\Framework\TestCase;
abstract class ApplicationFeatureTest extends TestCase
{
public static function setUpBeforeClass()
{
$_SERVER['HTTP_HOST'] = 'foo.bar';
require_once __DIR__ . '/../../includes/engelsystem.php';
}
}

View File

@ -3,18 +3,13 @@
namespace Engelsystem\Test\Feature\Logger; namespace Engelsystem\Test\Feature\Logger;
use Engelsystem\Logger\EngelsystemLogger; use Engelsystem\Logger\EngelsystemLogger;
use PHPUnit\Framework\TestCase; use Engelsystem\Test\Feature\ApplicationFeatureTest;
use Psr\Log\InvalidArgumentException; use Psr\Log\InvalidArgumentException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel; use Psr\Log\LogLevel;
class EngelsystemLoggerTest extends TestCase class EngelsystemLoggerTest extends ApplicationFeatureTest
{ {
public static function setUpBeforeClass()
{
require_once __DIR__ . '/../../../includes/engelsystem.php';
}
/** /**
* @return LoggerInterface * @return LoggerInterface
*/ */

View File

@ -2,16 +2,11 @@
namespace Engelsystem\Test\Feature\Model; namespace Engelsystem\Test\Feature\Model;
use PHPUnit\Framework\TestCase; use Engelsystem\Test\Feature\ApplicationFeatureTest;
use Psr\Log\LogLevel; use Psr\Log\LogLevel;
class LogEntriesModelTest extends TestCase class LogEntriesModelTest extends ApplicationFeatureTest
{ {
public static function setUpBeforeClass()
{
require_once __DIR__ . '/../../../includes/engelsystem.php';
}
public function testCreateLogEntry() public function testCreateLogEntry()
{ {
LogEntries_clear_all(); LogEntries_clear_all();

View File

@ -8,11 +8,6 @@ class RoomModelTest extends TestCase
{ {
private $room_id = null; private $room_id = null;
public static function setUpBeforeClass()
{
require_once __DIR__ . '/../../../includes/engelsystem.php';
}
public function createRoom() public function createRoom()
{ {
$this->room_id = Room_create('test', false, null, null); $this->room_id = Room_create('test', false, null, null);

View File

@ -9,6 +9,7 @@ use Engelsystem\Container\ServiceProvider;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject; use PHPUnit_Framework_MockObject_MockObject;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\Http\Server\MiddlewareInterface;
use ReflectionClass; use ReflectionClass;
class ApplicationTest extends TestCase class ApplicationTest extends TestCase
@ -118,6 +119,7 @@ class ApplicationTest extends TestCase
/** /**
* @covers \Engelsystem\Application::bootstrap * @covers \Engelsystem\Application::bootstrap
* @covers \Engelsystem\Application::isBooted * @covers \Engelsystem\Application::isBooted
* @covers \Engelsystem\Application::getMiddleware
*/ */
public function testBootstrap() public function testBootstrap()
{ {
@ -137,10 +139,11 @@ class ApplicationTest extends TestCase
$config = $this->getMockBuilder(Config::class) $config = $this->getMockBuilder(Config::class)
->getMock(); ->getMock();
$config->expects($this->once()) $middleware = [MiddlewareInterface::class];
$config->expects($this->exactly(2))
->method('get') ->method('get')
->with('providers') ->withConsecutive(['providers'], ['middleware'])
->willReturn([$serviceProvider]); ->willReturnOnConsecutiveCalls([$serviceProvider], $middleware);
$property = (new ReflectionClass($app))->getProperty('serviceProviders'); $property = (new ReflectionClass($app))->getProperty('serviceProviders');
$property->setAccessible(true); $property->setAccessible(true);
@ -149,6 +152,7 @@ class ApplicationTest extends TestCase
$app->bootstrap($config); $app->bootstrap($config);
$this->assertTrue($app->isBooted()); $this->assertTrue($app->isBooted());
$this->assertEquals($middleware, $app->getMiddleware());
// Run bootstrap another time to ensure that providers are registered only once // Run bootstrap another time to ensure that providers are registered only once
$app->bootstrap($config); $app->bootstrap($config);

View File

@ -49,15 +49,19 @@ class HandlerTest extends TestCase
public function testExceptionHandler() public function testExceptionHandler()
{ {
$exception = new Exception(); $exception = new Exception();
$errorMessage = 'Oh noes, an error!';
/** @var HandlerInterface|Mock $handlerMock */ /** @var HandlerInterface|Mock $handlerMock */
$handlerMock = $this->getMockForAbstractClass(HandlerInterface::class); $handlerMock = $this->getMockForAbstractClass(HandlerInterface::class);
$handlerMock->expects($this->once()) $handlerMock->expects($this->atLeastOnce())
->method('report') ->method('report')
->with($exception); ->with($exception);
$handlerMock->expects($this->once()) $handlerMock->expects($this->atLeastOnce())
->method('render') ->method('render')
->with($this->isInstanceOf(Request::class), $exception); ->with($this->isInstanceOf(Request::class), $exception)
->willReturnCallback(function () use ($errorMessage) {
echo $errorMessage;
});
/** @var Handler|Mock $handler */ /** @var Handler|Mock $handler */
$handler = $this->getMockBuilder(Handler::class) $handler = $this->getMockBuilder(Handler::class)
@ -68,7 +72,11 @@ class HandlerTest extends TestCase
$handler->setHandler(Handler::ENV_PRODUCTION, $handlerMock); $handler->setHandler(Handler::ENV_PRODUCTION, $handlerMock);
$this->expectOutputString($errorMessage);
$handler->exceptionHandler($exception); $handler->exceptionHandler($exception);
$return = $handler->exceptionHandler($exception, true);
$this->assertEquals($errorMessage, $return);
} }
/** /**

View File

@ -72,6 +72,14 @@ class WhoopsTest extends TestCase
[$prettyPageHandler], [$prettyPageHandler],
[$jsonResponseHandler] [$jsonResponseHandler]
); );
$whoopsRunner
->expects($this->once())
->method('writeToOutput')
->with(false);
$whoopsRunner
->expects($this->once())
->method('allowQuit')
->with(false);
$whoopsRunner $whoopsRunner
->expects($this->once()) ->expects($this->once())
->method('handleException') ->method('handleException')

View File

@ -6,6 +6,7 @@ use Engelsystem\Application;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Container\Container; use Engelsystem\Container\Container;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Renderer\Renderer; use Engelsystem\Renderer\Renderer;
use Engelsystem\Routing\UrlGeneratorInterface; use Engelsystem\Routing\UrlGeneratorInterface;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -126,6 +127,33 @@ class HelpersTest extends TestCase
$this->assertEquals('requestValue', request('requestKey')); $this->assertEquals('requestValue', request('requestKey'));
} }
/**
* @covers \response
*/
public function testResponse()
{
/** @var MockObject|Response $response */
$response = $this->getMockBuilder(Response::class)->getMock();
$this->getAppMock('psr7.response', $response);
$response->expects($this->once())
->method('withContent')
->with('Lorem Ipsum?')
->willReturn($response);
$response->expects($this->once())
->method('withStatus')
->with(501)
->willReturn($response);
$response->expects($this->exactly(2))
->method('withAddedHeader')
->withConsecutive(['lor', 'em'], ['foo', 'bar'])
->willReturn($response);
$this->assertEquals($response, response('Lorem Ipsum?', 501, ['lor' => 'em', 'foo' => 'bar',]));
}
/** /**
* @covers \session * @covers \session
*/ */

View File

@ -0,0 +1,50 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Test\Unit\Http\Stub\MessageTraitRequestImplementation;
use PHPUnit\Framework\TestCase;
use Zend\Diactoros\Stream;
class MessageTraitRequestTest extends TestCase
{
/**
* @covers \Engelsystem\Http\MessageTrait::withProtocolVersion
*/
public function testWithProtocolVersion()
{
$message = new MessageTraitRequestImplementation();
$newMessage = $message->withProtocolVersion('0.1');
$this->assertNotEquals($message, $newMessage);
$this->assertEquals('0.1', $newMessage->getProtocolVersion());
}
/**
* @covers \Engelsystem\Http\MessageTrait::getHeaders
*/
public function testGetHeaders()
{
$message = new MessageTraitRequestImplementation();
$newMessage = $message->withHeader('lorem', 'ipsum');
$this->assertNotEquals($message, $newMessage);
$this->assertArraySubset(['lorem' => ['ipsum']], $newMessage->getHeaders());
}
/**
* @covers \Engelsystem\Http\MessageTrait::withBody
*/
public function testWithBody()
{
/** @var Stream $stream */
$stream = new Stream('php://memory', 'wb+');
$stream->write('Test content');
$stream->rewind();
$message = new MessageTraitRequestImplementation();
$newMessage = $message->withBody($stream);
$this->assertNotEquals($message, $newMessage);
$this->assertEquals('Test content', $newMessage->getContent());
}
}

View File

@ -0,0 +1,159 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Test\Unit\Http\Stub\MessageTraitResponseImplementation;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\StreamInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Zend\Diactoros\Stream;
class MessageTraitResponseTest extends TestCase
{
/**
* @covers \Engelsystem\Http\MessageTrait
*/
public function testCreate()
{
$message = new MessageTraitResponseImplementation();
$this->assertInstanceOf(MessageInterface::class, $message);
$this->assertInstanceOf(SymfonyResponse::class, $message);
}
/**
* @covers \Engelsystem\Http\MessageTrait::getProtocolVersion
* @covers \Engelsystem\Http\MessageTrait::withProtocolVersion
*/
public function testGetProtocolVersion()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withProtocolVersion('0.1');
$this->assertNotEquals($message, $newMessage);
$this->assertEquals('0.1', $newMessage->getProtocolVersion());
}
/**
* @covers \Engelsystem\Http\MessageTrait::getHeaders
*/
public function testGetHeaders()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('Foo', 'bar');
$this->assertNotEquals($message, $newMessage);
$this->assertArraySubset(['Foo' => ['bar']], $newMessage->getHeaders());
$newMessage = $message->withHeader('lorem', ['ipsum', 'dolor']);
$this->assertArraySubset(['lorem' => ['ipsum', 'dolor']], $newMessage->getHeaders());
}
/**
* @covers \Engelsystem\Http\MessageTrait::hasHeader
*/
public function testHasHeader()
{
$message = new MessageTraitResponseImplementation();
$this->assertFalse($message->hasHeader('test'));
$newMessage = $message->withHeader('test', '12345');
$this->assertTrue($newMessage->hasHeader('Test'));
$this->assertTrue($newMessage->hasHeader('test'));
}
/**
* @covers \Engelsystem\Http\MessageTrait::getHeader
*/
public function testGetHeader()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', 'bar');
$this->assertEquals(['bar'], $newMessage->getHeader('Foo'));
$this->assertEquals([], $newMessage->getHeader('LoremIpsum'));
}
/**
* @covers \Engelsystem\Http\MessageTrait::getHeaderLine
*/
public function testGetHeaderLine()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', ['bar', 'bla']);
$this->assertEquals('', $newMessage->getHeaderLine('Lorem-Ipsum'));
$this->assertEquals('bar,bla', $newMessage->getHeaderLine('Foo'));
}
/**
* @covers \Engelsystem\Http\MessageTrait::withHeader
*/
public function testWithHeader()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', 'bar');
$this->assertNotEquals($message, $newMessage);
$this->assertArraySubset(['foo' => ['bar']], $newMessage->getHeaders());
$newMessage = $newMessage->withHeader('Foo', ['lorem', 'ipsum']);
$this->assertArraySubset(['Foo' => ['lorem', 'ipsum']], $newMessage->getHeaders());
}
/**
* @covers \Engelsystem\Http\MessageTrait::withAddedHeader
*/
public function testWithAddedHeader()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', 'bar');
$this->assertNotEquals($message, $newMessage);
$this->assertArraySubset(['foo' => ['bar']], $newMessage->getHeaders());
$newMessage = $newMessage->withAddedHeader('Foo', ['lorem', 'ipsum']);
$this->assertArraySubset(['Foo' => ['bar', 'lorem', 'ipsum']], $newMessage->getHeaders());
}
/**
* @covers \Engelsystem\Http\MessageTrait::withoutHeader
*/
public function testWithoutHeader()
{
$message = (new MessageTraitResponseImplementation())->withHeader('foo', 'bar');
$this->assertTrue($message->hasHeader('foo'));
$newMessage = $message->withoutHeader('Foo');
$this->assertNotEquals($message, $newMessage);
$this->assertFalse($newMessage->hasHeader('foo'));
}
/**
* @covers \Engelsystem\Http\MessageTrait::getBody
*/
public function testGetBody()
{
$message = (new MessageTraitResponseImplementation())->setContent('Foo bar!');
$body = $message->getBody();
$this->assertInstanceOf(StreamInterface::class, $body);
$this->assertEquals('Foo bar!', $body->getContents());
}
/**
* @covers \Engelsystem\Http\MessageTrait::withBody
*/
public function testWithBody()
{
/** @var Stream $stream */
$stream = new Stream('php://memory', 'wb+');
$stream->write('Test content');
$stream->rewind();
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withBody($stream);
$this->assertNotEquals($message, $newMessage);
$this->assertEquals('Test content', $newMessage->getContent());
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Psr7ServiceProvider;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Test\Unit\ServiceProviderTest;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as RequestInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
class Psr7ServiceProviderTest extends ServiceProviderTest
{
/**
* @covers \Engelsystem\Http\Psr7ServiceProvider::register()
*/
public function testRegister()
{
/** @var MockObject|DiactorosFactory $psr7Factory */
$psr7Factory = $this->createMock(DiactorosFactory::class);
/** @var MockObject|Request $request */
$request = $this->createMock(Request::class);
/** @var MockObject|Response $response */
$response = $this->createMock(Response::class);
/** @var MockObject|RequestInterface $psr7request */
$psr7request = $this->createMock(Request::class);
/** @var MockObject|ResponseInterface $psr7response */
$psr7response = $this->createMock(Response::class);
$app = $this->getApp(['make', 'instance', 'get', 'bind']);
$this->setExpects($app, 'make', [DiactorosFactory::class], $psr7Factory);
$app->expects($this->atLeastOnce())
->method('get')
->withConsecutive(['request'], ['response'])
->willReturnOnConsecutiveCalls($request, $response);
$app->expects($this->atLeastOnce())
->method('instance')
->withConsecutive(
['psr7.factory', $psr7Factory],
['psr7.request', $psr7request],
['psr7.response', $psr7response]
);
$app->expects($this->atLeastOnce())
->method('bind')
->withConsecutive(
[RequestInterface::class, 'psr7.request'],
[ResponseInterface::class, 'psr7.response']
);
$serviceProvider = new Psr7ServiceProvider($app);
$serviceProvider->register();
}
}

View File

@ -5,9 +5,24 @@ namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject; use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyFile;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class RequestTest extends TestCase class RequestTest extends TestCase
{ {
/**
* @covers \Engelsystem\Http\Request
*/
public function testCreate()
{
$response = new Request();
$this->assertInstanceOf(SymfonyRequest::class, $response);
$this->assertInstanceOf(RequestInterface::class, $response);
}
/** /**
* @covers \Engelsystem\Http\Request::postData * @covers \Engelsystem\Http\Request::postData
*/ */
@ -96,4 +111,272 @@ class RequestTest extends TestCase
$this->assertEquals('http://foo.bar/bla/foo', $request->url()); $this->assertEquals('http://foo.bar/bla/foo', $request->url());
$this->assertEquals('https://lorem.ipsum/dolor/sit', $request->url()); $this->assertEquals('https://lorem.ipsum/dolor/sit', $request->url());
} }
/**
* @covers \Engelsystem\Http\Request::getRequestTarget
*/
public function testGetRequestTarget()
{
/** @var Request|MockObject $request */
$request = $this
->getMockBuilder(Request::class)
->setMethods(['getQueryString', 'path'])
->getMock();
$request->expects($this->exactly(2))
->method('getQueryString')
->willReturnOnConsecutiveCalls(null, 'foo=bar&lorem=ipsum');
$request->expects($this->exactly(2))
->method('path')
->willReturn('foo/bar');
$this->assertEquals('/foo/bar', $request->getRequestTarget());
$this->assertEquals('/foo/bar?foo=bar&lorem=ipsum', $request->getRequestTarget());
}
/**
* @covers \Engelsystem\Http\Request::withRequestTarget
*/
public function testWithRequestTarget()
{
$request = new Request();
foreach (
[
'*',
'/foo/bar',
'https://lorem.ipsum/test?lor=em'
] as $target
) {
$new = $request->withRequestTarget($target);
$this->assertNotEquals($request, $new);
}
}
/**
* @covers \Engelsystem\Http\Request::withMethod
*/
public function testWithMethod()
{
$request = new Request();
$new = $request->withMethod('PUT');
$this->assertNotEquals($request, $new);
$this->assertEquals('PUT', $new->getMethod());
}
/**
* @covers \Engelsystem\Http\Request::withUri
*/
public function testWithUri()
{
/** @var UriInterface|MockObject $uri */
$uri = $this->getMockForAbstractClass(UriInterface::class);
$uri->expects($this->atLeastOnce())
->method('__toString')
->willReturn('http://foo.bar/bla?foo=bar');
$request = Request::create('http://lor.em/');
$new = $request->withUri($uri);
$this->assertNotEquals($request, $new);
$this->assertEquals('http://foo.bar/bla?foo=bar', (string)$new->getUri());
$new = $request->withUri($uri, true);
$this->assertEquals('http://lor.em/bla?foo=bar', (string)$new->getUri());
}
/**
* @covers \Engelsystem\Http\Request::getUri
*/
public function testGetUri()
{
$request = Request::create('http://lor.em/test?bla=foo');
$uri = $request->getUri();
$this->assertInstanceOf(UriInterface::class, $uri);
$this->assertEquals('http://lor.em/test?bla=foo', (string)$uri);
}
/**
* @covers \Engelsystem\Http\Request::getServerParams
*/
public function testGetServerParams()
{
$server = ['foo' => 'bar'];
$request = new Request([], [], [], [], [], $server);
$this->assertEquals($server, $request->getServerParams());
}
/**
* @covers \Engelsystem\Http\Request::getCookieParams
*/
public function testGetCookieParams()
{
$cookies = ['session' => 'LoremIpsumDolorSit'];
$request = new Request([], [], [], $cookies);
$this->assertEquals($cookies, $request->getCookieParams());
}
/**
* @covers \Engelsystem\Http\Request::withCookieParams
*/
public function testWithCookieParams()
{
$cookies = ['lor' => 'em'];
$request = new Request();
$new = $request->withCookieParams($cookies);
$this->assertNotEquals($request, $new);
$this->assertEquals($cookies, $new->getCookieParams());
}
/**
* @covers \Engelsystem\Http\Request::getQueryParams
*/
public function testGetQueryParams()
{
$params = ['foo' => 'baz'];
$request = new Request($params);
$this->assertEquals($params, $request->getQueryParams());
}
/**
* @covers \Engelsystem\Http\Request::withQueryParams
*/
public function testWithQueryParams()
{
$params = ['test' => 'ing'];
$request = new Request();
$new = $request->withQueryParams($params);
$this->assertNotEquals($request, $new);
$this->assertEquals($params, $new->getQueryParams());
}
/**
* @covers \Engelsystem\Http\Request::getUploadedFiles
*/
public function testGetUploadedFiles()
{
$filename = tempnam(sys_get_temp_dir(), 'test');
file_put_contents($filename, 'LoremIpsum!');
$files = [new SymfonyFile($filename, 'foo.html', 'text/html', 11)];
$request = new Request([], [], [], [], $files);
$uploadedFiles = $request->getUploadedFiles();
$this->assertNotEmpty($uploadedFiles);
/** @var UploadedFileInterface $file */
$file = $uploadedFiles[0];
$this->assertInstanceOf(UploadedFileInterface::class, $file);
$this->assertEquals('foo.html', $file->getClientFilename());
$this->assertEquals('text/html', $file->getClientMediaType());
$this->assertEquals(11, $file->getSize());
}
/**
* @covers \Engelsystem\Http\Request::withUploadedFiles
*/
public function testWithUploadedFiles()
{
$filename = tempnam(sys_get_temp_dir(), 'test');
file_put_contents($filename, 'LoremIpsum!');
$file = new \Zend\Diactoros\UploadedFile($filename, 11, UPLOAD_ERR_OK, 'test.txt', 'text/plain');
$request = new Request();
$new = $request->withUploadedFiles([$file]);
$uploadedFiles = $new->getUploadedFiles();
$this->assertNotEquals($request, $new);
$this->assertNotEmpty($uploadedFiles);
/** @var UploadedFileInterface $file */
$file = $uploadedFiles[0];
$this->assertEquals('test.txt', $file->getClientFilename());
$this->assertEquals('text/plain', $file->getClientMediaType());
$this->assertEquals(11, $file->getSize());
}
/**
* @covers \Engelsystem\Http\Request::getParsedBody
*/
public function testGetParsedBody()
{
$body = ['foo' => 'lorem'];
$request = new Request();
$request->request->add($body);
$this->assertEquals($body, $request->getParsedBody());
}
/**
* @covers \Engelsystem\Http\Request::withParsedBody
*/
public function testWithParsedBody()
{
$data = ['test' => 'er'];
$request = new Request();
$new = $request->withParsedBody($data);
$this->assertNotEquals($request, $new);
$this->assertEquals($data, $new->getParsedBody());
}
/**
* @covers \Engelsystem\Http\Request::getAttributes
*/
public function testGetAttributes()
{
$attributes = ['foo' => 'lorem', 'ipsum' => 'dolor'];
$request = new Request([], [], $attributes);
$this->assertEquals($attributes, $request->getAttributes());
}
/**
* @covers \Engelsystem\Http\Request::getAttribute
*/
public function testGetAttribute()
{
$attributes = ['foo' => 'lorem', 'ipsum' => 'dolor'];
$request = new Request([], [], $attributes);
$this->assertEquals($attributes['ipsum'], $request->getAttribute('ipsum'));
$this->assertEquals(null, $request->getAttribute('dolor'));
$this->assertEquals(1234, $request->getAttribute('test', 1234));
}
/**
* @covers \Engelsystem\Http\Request::withAttribute
*/
public function testWithAttribute()
{
$request = new Request();
$new = $request->withAttribute('lorem', 'ipsum');
$this->assertNotEquals($request, $new);
$this->assertEquals('ipsum', $new->getAttribute('lorem'));
}
/**
* @covers \Engelsystem\Http\Request::withoutAttribute
*/
public function testWithoutAttribute()
{
$attributes = ['foo' => 'lorem', 'ipsum' => 'dolor'];
$request = new Request([], [], $attributes);
$new = $request->withoutAttribute('ipsum');
$this->assertNotEquals($request, $new);
$this->assertEquals(['foo' => 'lorem'], $new->getAttributes());
}
} }

View File

@ -0,0 +1,29 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Response;
use Engelsystem\Http\ResponseServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
class ResponseServiceProviderTest extends ServiceProviderTest
{
/**
* @covers \Engelsystem\Http\ResponseServiceProvider::register()
*/
public function testRegister()
{
/** @var MockObject|Response $response */
$response = $this->getMockBuilder(Response::class)
->getMock();
$app = $this->getApp();
$this->setExpects($app, 'make', [Response::class], $response);
$this->setExpects($app, 'instance', ['response', $response]);
$serviceProvider = new ResponseServiceProvider($app);
$serviceProvider->register();
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Response;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
class ResponseTest extends TestCase
{
/**
* @covers \Engelsystem\Http\Response
*/
public function testCreate()
{
$response = new Response();
$this->assertInstanceOf(SymfonyResponse::class, $response);
$this->assertInstanceOf(ResponseInterface::class, $response);
}
/**
* @covers \Engelsystem\Http\Response::withStatus
* @covers \Engelsystem\Http\Response::getReasonPhrase
*/
public function testWithStatus()
{
$response = new Response();
$newResponse = $response->withStatus(503);
$this->assertNotEquals($response, $newResponse);
$this->assertNotEquals('', $newResponse->getReasonPhrase());
$this->assertEquals(503, $newResponse->getStatusCode());
$newResponse = $response->withStatus(503, 'Foo');
$this->assertEquals('Foo', $newResponse->getReasonPhrase());
}
/**
* @covers \Engelsystem\Http\Response::withContent
*/
public function testWithContent()
{
$response = new Response();
$newResponse = $response->withContent('Lorem Ipsum?');
$this->assertNotEquals($response, $newResponse);
$this->assertEquals('Lorem Ipsum?', $newResponse->getContent());
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Engelsystem\Test\Unit\Http\Stub;
use Engelsystem\Http\MessageTrait;
use Psr\Http\Message\MessageInterface;
use Symfony\Component\HttpFoundation\Request;
class MessageTraitRequestImplementation extends Request implements MessageInterface
{
use MessageTrait;
}

View File

@ -0,0 +1,12 @@
<?php
namespace Engelsystem\Test\Unit\Http\Stub;
use Engelsystem\Http\MessageTrait;
use Psr\Http\Message\MessageInterface;
use Symfony\Component\HttpFoundation\Response;
class MessageTraitResponseImplementation extends Response implements MessageInterface
{
use MessageTrait;
}

View File

@ -0,0 +1,230 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Application;
use Engelsystem\Middleware\Dispatcher;
use Engelsystem\Test\Unit\Middleware\Stub\NotARealMiddleware;
use Engelsystem\Test\Unit\Middleware\Stub\ReturnResponseMiddleware;
use InvalidArgumentException;
use LogicException;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use ReflectionClass as Reflection;
class DispatcherTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\Dispatcher::__construct
*/
public function testInit()
{
/** @var Application|MockObject $container */
$container = $this->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);
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Application;
use Engelsystem\Exceptions\Handler;
use Engelsystem\Http\Response;
use Engelsystem\Middleware\ExceptionHandler;
use Engelsystem\Test\Unit\Middleware\Stub\ExceptionMiddlewareHandler;
use Engelsystem\Test\Unit\Middleware\Stub\ReturnResponseMiddlewareHandler;
use Illuminate\Contracts\Container\Container as ContainerInterface;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class ExceptionHandlerTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\ExceptionHandler::__construct
* @covers \Engelsystem\Middleware\ExceptionHandler::process
*/
public function testRegister()
{
/** @var MockObject|ContainerInterface $container */
$container = $this->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);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Http\Request;
use Engelsystem\Middleware\LegacyMiddleware;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
class LegacyMiddlewareTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\LegacyMiddleware::__construct
* @covers \Engelsystem\Middleware\LegacyMiddleware::process
*/
public function testRegister()
{
/** @var ContainerInterface|MockObject $container */
$container = $this->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);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Middleware\NotFoundResponse;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class NotFoundResponseTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\NotFoundResponse::process
*/
public function testRegister()
{
/** @var NotFoundResponse|MockObject $middleware */
$middleware = $this->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);
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Middleware\SendResponseHandler;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class SendResponseHandlerTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\SendResponseHandler::process
*/
public function testRegister()
{
/** @var SendResponseHandler|MockObject $middleware */
$middleware = $this->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);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Engelsystem\Test\Unit\Middleware\Stub;
use Exception;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ExceptionMiddlewareHandler implements RequestHandlerInterface
{
/**
* Throws an exception
*
* @param ServerRequestInterface $request
* @return ResponseInterface
* @throws Exception
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
throw new Exception('Boooom!');
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Engelsystem\Test\Unit\Middleware\Stub;
class NotARealMiddleware
{
// I'm not a middleware!
}

View File

@ -0,0 +1,36 @@
<?php
namespace Engelsystem\Test\Unit\Middleware\Stub;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ReturnResponseMiddleware implements MiddlewareInterface
{
/** @var ResponseInterface */
protected $response;
public function __construct(ResponseInterface $response)
{
$this->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;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Engelsystem\Test\Unit\Middleware\Stub;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ReturnResponseMiddlewareHandler implements RequestHandlerInterface
{
/** @var ResponseInterface */
protected $response;
public function __construct(ResponseInterface $response)
{
$this->response = $response;
}
/**
* Returns a given response
*
* @param ServerRequestInterface $request
* @return ResponseInterface
* @throws \Exception
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->response;
}
}