summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Scheller <igor.scheller@igorshp.de>2018-09-08 12:48:08 +0200
committerIgor Scheller <igor.scheller@igorshp.de>2018-09-09 12:11:12 +0200
commit222c9fed7d4ca2b3b44c05907fbb7080c1efd342 (patch)
tree62b1bfa9233000985331b9dea703d51dc24515a2
parent2d6bca1357faff28bc1f86a56b432cc463ff7574 (diff)
parent8257864829ffdfb410f05e0dd0a9c781f48b741a (diff)
Merge remote-tracking branch 'MyIgel/templating'
-rw-r--r--composer.json2
-rw-r--r--config/app.php4
-rw-r--r--includes/engelsystem.php8
-rw-r--r--includes/helper/email_helper.php8
-rw-r--r--includes/helper/internationalization_helper.php80
-rw-r--r--includes/includes.php1
-rw-r--r--includes/pages/guest_credits.php2
-rw-r--r--includes/pages/user_shifts.php2
-rw-r--r--includes/sys_form.php17
-rw-r--r--includes/sys_menu.php26
-rw-r--r--src/Application.php1
-rw-r--r--src/Config/ConfigServiceProvider.php1
-rw-r--r--src/Helpers/TranslationServiceProvider.php58
-rw-r--r--src/Helpers/Translator.php105
-rw-r--r--src/Http/Response.php41
-rw-r--r--src/Http/SessionServiceProvider.php1
-rw-r--r--src/Http/UrlGeneratorServiceProvider.php1
-rw-r--r--src/Middleware/ErrorHandler.php80
-rw-r--r--src/Middleware/LegacyMiddleware.php24
-rw-r--r--src/Middleware/SetLocale.php49
-rw-r--r--src/Renderer/RendererServiceProvider.php2
-rw-r--r--src/Renderer/Twig/Extensions/Config.php31
-rw-r--r--src/Renderer/Twig/Extensions/Globals.php23
-rw-r--r--src/Renderer/Twig/Extensions/Session.php31
-rw-r--r--src/Renderer/Twig/Extensions/Translation.php57
-rw-r--r--src/Renderer/Twig/Extensions/Url.php43
-rw-r--r--src/Renderer/TwigEngine.php41
-rw-r--r--src/Renderer/TwigLoader.php26
-rw-r--r--src/Renderer/TwigServiceProvider.php77
-rw-r--r--src/helpers.php39
-rw-r--r--templates/errors/default.twig7
-rw-r--r--templates/layout.html46
-rw-r--r--templates/layouts/app.twig87
-rw-r--r--templates/layouts/maintenance.html (renamed from templates/maintenance.html)0
-rw-r--r--templates/pages/credits.html (renamed from templates/guest_credits.html)18
-rw-r--r--templates/pages/user-shifts.html (renamed from templates/user_shifts.html)0
-rw-r--r--tests/Unit/ApplicationTest.php1
-rw-r--r--tests/Unit/Config/ConfigServiceProviderTest.php14
-rw-r--r--tests/Unit/Helpers/TranslationServiceProviderTest.php86
-rw-r--r--tests/Unit/Helpers/TranslatorTest.php69
-rw-r--r--tests/Unit/HelpersTest.php25
-rw-r--r--tests/Unit/Http/ResponseTest.php37
-rw-r--r--tests/Unit/Http/SessionServiceProviderTest.php4
-rw-r--r--tests/Unit/Http/UrlGeneratorServiceProviderTest.php7
-rw-r--r--tests/Unit/Middleware/ErrorHandlerTest.php88
-rw-r--r--tests/Unit/Middleware/SetLocaleTest.php71
-rw-r--r--tests/Unit/Middleware/Stub/ReturnResponseMiddlewareHandler.php10
-rw-r--r--tests/Unit/Renderer/RendererServiceProviderTest.php4
-rw-r--r--tests/Unit/Renderer/Twig/Extensions/ConfigTest.php25
-rw-r--r--tests/Unit/Renderer/Twig/Extensions/ExtensionTest.php84
-rw-r--r--tests/Unit/Renderer/Twig/Extensions/GlobalsTest.php25
-rw-r--r--tests/Unit/Renderer/Twig/Extensions/SessionTest.php25
-rw-r--r--tests/Unit/Renderer/Twig/Extensions/TranslationTest.php60
-rw-r--r--tests/Unit/Renderer/Twig/Extensions/UrlTest.php64
-rw-r--r--tests/Unit/Renderer/TwigEngineTest.php60
-rw-r--r--tests/Unit/Renderer/TwigLoaderTest.php31
-rw-r--r--tests/Unit/Renderer/TwigServiceProviderTest.php155
57 files changed, 1801 insertions, 183 deletions
diff --git a/composer.json b/composer.json
index f38bb972..1f53e546 100644
--- a/composer.json
+++ b/composer.json
@@ -29,6 +29,8 @@
"symfony/http-foundation": "^3.3",
"symfony/psr-http-message-bridge": "^1.0",
"twbs/bootstrap": "^3.3",
+ "twig/extensions": "^1.5",
+ "twig/twig": "^2.5",
"zendframework/zend-diactoros": "^1.7"
},
"require-dev": {
diff --git a/config/app.php b/config/app.php
index 9af35eb4..e309abe4 100644
--- a/config/app.php
+++ b/config/app.php
@@ -13,8 +13,10 @@ return [
\Engelsystem\Database\DatabaseServiceProvider::class,
\Engelsystem\Http\RequestServiceProvider::class,
\Engelsystem\Http\SessionServiceProvider::class,
+ \Engelsystem\Helpers\TranslationServiceProvider::class,
\Engelsystem\Http\ResponseServiceProvider::class,
\Engelsystem\Http\Psr7ServiceProvider::class,
+ \Engelsystem\Renderer\TwigServiceProvider::class,
\Engelsystem\Middleware\RouteDispatcherServiceProvider::class,
\Engelsystem\Middleware\RequestHandlerServiceProvider::class,
],
@@ -23,6 +25,8 @@ return [
'middleware' => [
\Engelsystem\Middleware\SendResponseHandler::class,
\Engelsystem\Middleware\ExceptionHandler::class,
+ \Engelsystem\Middleware\SetLocale::class,
+ \Engelsystem\Middleware\ErrorHandler::class,
\Engelsystem\Middleware\RouteDispatcher::class,
\Engelsystem\Middleware\RequestHandler::class,
],
diff --git a/includes/engelsystem.php b/includes/engelsystem.php
index f7d813c5..4c096b43 100644
--- a/includes/engelsystem.php
+++ b/includes/engelsystem.php
@@ -16,18 +16,12 @@ require __DIR__ . '/includes.php';
* Check for maintenance
*/
if ($app->get('config')->get('maintenance')) {
- echo file_get_contents(__DIR__ . '/../templates/maintenance.html');
+ echo file_get_contents(__DIR__ . '/../templates/layouts/maintenance.html');
die();
}
/**
- * Init translations
- */
-gettext_init();
-
-
-/**
* Init authorization
*/
load_auth();
diff --git a/includes/helper/email_helper.php b/includes/helper/email_helper.php
index c223d026..8d1cb794 100644
--- a/includes/helper/email_helper.php
+++ b/includes/helper/email_helper.php
@@ -15,14 +15,16 @@ function engelsystem_email_to_user($recipient_user, $title, $message, $not_if_it
return true;
}
- gettext_locale($recipient_user['Sprache']);
+ /** @var \Engelsystem\Helpers\Translator $translator */
+ $translator = app()->get('translator');
+ $locale = $translator->getLocale();
+ $translator->setLocale($recipient_user['Sprache']);
$message = sprintf(_('Hi %s,'), $recipient_user['Nick']) . "\n\n"
. _('here is a message for you from the engelsystem:') . "\n\n"
. $message . "\n\n"
. _('This email is autogenerated and has not been signed. You got this email because you are registered in the engelsystem.');
-
- gettext_locale();
+ $translator->setLocale($locale);
return engelsystem_email($recipient_user['email'], $title, $message);
}
diff --git a/includes/helper/internationalization_helper.php b/includes/helper/internationalization_helper.php
deleted file mode 100644
index bb6d0abd..00000000
--- a/includes/helper/internationalization_helper.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-/**
- * Return currently active locale
- *
- * @return string
- */
-function locale()
-{
- return session()->get('locale');
-}
-
-/**
- * Returns two letter language code from currently active locale
- *
- * @return string
- */
-function locale_short()
-{
- return substr(locale(), 0, 2);
-}
-
-/**
- * Initializes gettext for internationalization and updates the sessions locale to use for translation.
- */
-function gettext_init()
-{
- $locales = config('locales');
- $request = request();
- $session = session();
-
- if ($request->has('set_locale') && isset($locales[$request->input('set_locale')])) {
- $session->set('locale', $request->input('set_locale'));
- } elseif (!$session->has('locale')) {
- $session->set('locale', config('default_locale'));
- }
-
- gettext_locale();
- bindtextdomain('default', app('path.lang'));
- bind_textdomain_codeset('default', 'UTF-8');
- textdomain('default');
-}
-
-/**
- * Swich gettext locale.
- *
- * @param string $locale
- */
-function gettext_locale($locale = null)
-{
- if (empty($locale)) {
- $locale = session()->get('locale');
- }
-
- putenv('LC_ALL=' . $locale);
- setlocale(LC_ALL, $locale);
-}
-
-/**
- * Renders language selection.
- *
- * @return array
- */
-function make_langselect()
-{
- $request = app('request');
-
- $items = [];
- foreach (config('locales') as $locale => $name) {
- $url = url($request->getPathInfo(), ['set_locale' => $locale]);
-
- $items[] = toolbar_item_link(
- htmlspecialchars($url),
- '',
- $name,
- $locale == locale()
- );
- }
- return $items;
-}
diff --git a/includes/includes.php b/includes/includes.php
index e316e550..7a68c3c9 100644
--- a/includes/includes.php
+++ b/includes/includes.php
@@ -61,7 +61,6 @@ $includeFiles = [
__DIR__ . '/../includes/controller/user_worklog_controller.php',
__DIR__ . '/../includes/helper/graph_helper.php',
- __DIR__ . '/../includes/helper/internationalization_helper.php',
__DIR__ . '/../includes/helper/message_helper.php',
__DIR__ . '/../includes/helper/error_helper.php',
__DIR__ . '/../includes/helper/email_helper.php',
diff --git a/includes/pages/guest_credits.php b/includes/pages/guest_credits.php
index db86132d..ecfa8f7c 100644
--- a/includes/pages/guest_credits.php
+++ b/includes/pages/guest_credits.php
@@ -13,5 +13,5 @@ function credits_title()
*/
function guest_credits()
{
- return view(__DIR__ . '/../../templates/guest_credits.html');
+ return view(__DIR__ . '/../../templates/pages/credits.html');
}
diff --git a/includes/pages/user_shifts.php b/includes/pages/user_shifts.php
index 186301db..020cfe54 100644
--- a/includes/pages/user_shifts.php
+++ b/includes/pages/user_shifts.php
@@ -224,7 +224,7 @@ function view_user_shifts()
return page([
div('col-md-12', [
msg(),
- view(__DIR__ . '/../../templates/user_shifts.html', [
+ view(__DIR__ . '/../../templates/pages/user-shifts.html', [
'title' => shifts_title(),
'room_select' => make_select($rooms, $shiftsFilter->getRooms(), 'rooms', _('Rooms')),
'start_select' => html_select_key(
diff --git a/includes/sys_form.php b/includes/sys_form.php
index c974c1d1..cd1c84e6 100644
--- a/includes/sys_form.php
+++ b/includes/sys_form.php
@@ -66,6 +66,9 @@ function form_date($name, $label, $value, $start_date = '', $end_date = '')
$value = is_numeric($value) ? date('Y-m-d', $value) : '';
$start_date = is_numeric($start_date) ? date('Y-m-d', $start_date) : '';
$end_date = is_numeric($end_date) ? date('Y-m-d', $end_date) : '';
+ $locale = $locale = session()->get('locale');
+ $shortLocale = substr($locale, 0, 2);
+
return form_element($label, '
<div class="input-group date" id="' . $dom_id . '">
<input name="' . $name . '" class="form-control" value="' . htmlspecialchars($value) . '">'
@@ -73,13 +76,13 @@ function form_date($name, $label, $value, $start_date = '', $end_date = '')
</div>
<script type="text/javascript">
$(function(){
- $(\'#' . $dom_id . '\').datepicker({
- language: \'' . locale_short() . '\',
- todayBtn: \'linked\',
- format: \'yyyy-mm-dd\',
- startDate: \'' . $start_date . '\',
- endDate: \'' . $end_date . '\',
- orientation: \'bottom\'
+ $("#' . $dom_id . '").datepicker({
+ language: "' . $shortLocale . '",
+ todayBtn: "linked",
+ format: "yyyy-mm-dd",
+ startDate: "' . $start_date . '",
+ endDate: "' . $end_date . '",
+ orientation: "bottom"
});
});
</script>
diff --git a/includes/sys_menu.php b/includes/sys_menu.php
index fae94de5..5609c0ab 100644
--- a/includes/sys_menu.php
+++ b/includes/sys_menu.php
@@ -110,7 +110,7 @@ function make_user_submenu()
{
global $privileges, $page;
- $user_submenu = make_langselect();
+ $user_submenu = make_language_select();
if (in_array('user_settings', $privileges) || in_array('logout', $privileges)) {
$user_submenu[] = toolbar_item_divider();
@@ -228,6 +228,30 @@ function make_room_navigation($menu)
}
/**
+ * Renders language selection.
+ *
+ * @return array
+ */
+function make_language_select()
+{
+ $request = app('request');
+ $activeLocale = session()->get('locale');
+
+ $items = [];
+ foreach (config('locales') as $locale => $name) {
+ $url = url($request->getPathInfo(), ['set-locale' => $locale]);
+
+ $items[] = toolbar_item_link(
+ htmlspecialchars($url),
+ '',
+ $name,
+ $locale == $activeLocale
+ );
+ }
+ return $items;
+}
+
+/**
* @return string
*/
function make_menu()
diff --git a/src/Application.php b/src/Application.php
index 6644a6cf..86397a2c 100644
--- a/src/Application.php
+++ b/src/Application.php
@@ -107,6 +107,7 @@ class Application extends Container
$this->instance('path', $appPath);
$this->instance('path.config', $appPath . DIRECTORY_SEPARATOR . 'config');
$this->instance('path.lang', $appPath . DIRECTORY_SEPARATOR . 'locale');
+ $this->instance('path.views', $appPath . DIRECTORY_SEPARATOR . 'templates');
}
/**
diff --git a/src/Config/ConfigServiceProvider.php b/src/Config/ConfigServiceProvider.php
index 9fbccd68..63f43ced 100644
--- a/src/Config/ConfigServiceProvider.php
+++ b/src/Config/ConfigServiceProvider.php
@@ -13,6 +13,7 @@ class ConfigServiceProvider extends ServiceProvider
public function register()
{
$config = $this->app->make(Config::class);
+ $this->app->instance(Config::class, $config);
$this->app->instance('config', $config);
foreach ($this->configFiles as $file) {
diff --git a/src/Helpers/TranslationServiceProvider.php b/src/Helpers/TranslationServiceProvider.php
new file mode 100644
index 00000000..9d86df7d
--- /dev/null
+++ b/src/Helpers/TranslationServiceProvider.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Engelsystem\Helpers;
+
+use Engelsystem\Config\Config;
+use Engelsystem\Container\ServiceProvider;
+use Symfony\Component\HttpFoundation\Session\Session;
+
+class TranslationServiceProvider extends ServiceProvider
+{
+ public function register()
+ {
+ /** @var Config $config */
+ $config = $this->app->get('config');
+ /** @var Session $session */
+ $session = $this->app->get('session');
+
+ $locales = $config->get('locales');
+ $locale = $config->get('default_locale');
+
+ $sessionLocale = $session->get('locale', $locale);
+ if (isset($locales[$sessionLocale])) {
+ $locale = $sessionLocale;
+ }
+
+ $this->initGettext();
+ $session->set('locale', $locale);
+
+ $translator = $this->app->make(
+ Translator::class,
+ ['locale' => $locale, 'locales' => $locales, 'localeChangeCallback' => [$this, 'setLocale']]
+ );
+ $this->app->instance(Translator::class, $translator);
+ $this->app->instance('translator', $translator);
+ }
+
+ /**
+ * @param string $textDomain
+ * @param string $encoding
+ * @codeCoverageIgnore
+ */
+ protected function initGettext($textDomain = 'default', $encoding = 'UTF-8')
+ {
+ bindtextdomain($textDomain, $this->app->get('path.lang'));
+ bind_textdomain_codeset($textDomain, $encoding);
+ textdomain($textDomain);
+ }
+
+ /**
+ * @param string $locale
+ * @codeCoverageIgnore
+ */
+ public function setLocale($locale)
+ {
+ putenv('LC_ALL=' . $locale);
+ setlocale(LC_ALL, $locale);
+ }
+}
diff --git a/src/Helpers/Translator.php b/src/Helpers/Translator.php
new file mode 100644
index 00000000..1e953a21
--- /dev/null
+++ b/src/Helpers/Translator.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Engelsystem\Helpers;
+
+class Translator
+{
+ /** @var string[] */
+ protected $locales;
+
+ /** @var string */
+ protected $locale;
+
+ /** @var callable */
+ protected $localeChangeCallback;
+
+ /**
+ * Translator constructor.
+ *
+ * @param string $locale
+ * @param string[] $locales
+ * @param callable $localeChangeCallback
+ */
+ public function __construct(string $locale, array $locales = [], callable $localeChangeCallback = null)
+ {
+ $this->localeChangeCallback = $localeChangeCallback;
+
+ $this->setLocale($locale);
+ $this->setLocales($locales);
+ }
+
+ /**
+ * Get the translation for a given key
+ *
+ * @param string $key
+ * @param array $replace
+ * @return string
+ */
+ public function translate(string $key, array $replace = []): string
+ {
+ $translated = $this->translateGettext($key);
+
+ if (!empty($replace)) {
+ $translated = call_user_func_array('sprintf', array_merge([$translated], $replace));
+ }
+
+ return $translated;
+ }
+
+ /**
+ * Translate the key via gettext
+ *
+ * @param string $key
+ * @return string
+ * @codeCoverageIgnore
+ */
+ protected function translateGettext(string $key): string
+ {
+ return _($key);
+ }
+
+ /**
+ * @return string
+ */
+ public function getLocale(): string
+ {
+ return $this->locale;
+ }
+
+ /**
+ * @param string $locale
+ */
+ public function setLocale(string $locale)
+ {
+ $this->locale = $locale;
+
+ if (is_callable($this->localeChangeCallback)) {
+ call_user_func_array($this->localeChangeCallback, [$locale]);
+ }
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getLocales(): array
+ {
+ return $this->locales;
+ }
+
+ /**
+ * @param string $locale
+ * @return bool
+ */
+ public function hasLocale(string $locale): bool
+ {
+ return isset($this->locales[$locale]);
+ }
+
+ /**
+ * @param string[] $locales
+ */
+ public function setLocales(array $locales)
+ {
+ $this->locales = $locales;
+ }
+}
diff --git a/src/Http/Response.php b/src/Http/Response.php
index 9db6fa83..d79ab98b 100644
--- a/src/Http/Response.php
+++ b/src/Http/Response.php
@@ -2,6 +2,7 @@
namespace Engelsystem\Http;
+use Engelsystem\Renderer\Renderer;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
@@ -9,6 +10,25 @@ class Response extends SymfonyResponse implements ResponseInterface
{
use MessageTrait;
+ /** @var Renderer */
+ protected $view;
+
+ /**
+ * @param string $content
+ * @param int $status
+ * @param array $headers
+ * @param Renderer $view
+ */
+ public function __construct(
+ $content = '',
+ int $status = 200,
+ array $headers = array(),
+ Renderer $view = null
+ ) {
+ $this->view = $view;
+ parent::__construct($content, $status, $headers);
+ }
+
/**
* Return an instance with the specified status code and, optionally, reason phrase.
*
@@ -72,4 +92,25 @@ class Response extends SymfonyResponse implements ResponseInterface
return $new;
}
+
+ /**
+ * Return an instance with the rendered content.
+ *
+ * THis method retains the immutability of the message and returns
+ * an instance with the updated status and headers
+ *
+ * @param string $view
+ * @param array $data
+ * @param int $status
+ * @param string[]|string[][] $headers
+ * @return Response
+ */
+ public function withView($view, $data = [], $status = 200, $headers = [])
+ {
+ if (!$this->view instanceof Renderer) {
+ throw new \InvalidArgumentException('Renderer not defined');
+ }
+
+ return $this->create($this->view->render($view, $data), $status, $headers);
+ }
}
diff --git a/src/Http/SessionServiceProvider.php b/src/Http/SessionServiceProvider.php
index 55e3f48b..59121a3b 100644
--- a/src/Http/SessionServiceProvider.php
+++ b/src/Http/SessionServiceProvider.php
@@ -17,6 +17,7 @@ class SessionServiceProvider extends ServiceProvider
$this->app->bind(SessionStorageInterface::class, 'session.storage');
$session = $this->app->make(Session::class);
+ $this->app->instance(Session::class, $session);
$this->app->instance('session', $session);
/** @var Request $request */
diff --git a/src/Http/UrlGeneratorServiceProvider.php b/src/Http/UrlGeneratorServiceProvider.php
index 37304076..9b9988aa 100644
--- a/src/Http/UrlGeneratorServiceProvider.php
+++ b/src/Http/UrlGeneratorServiceProvider.php
@@ -9,6 +9,7 @@ class UrlGeneratorServiceProvider extends ServiceProvider
public function register()
{
$urlGenerator = $this->app->make(UrlGenerator::class);
+ $this->app->instance(UrlGenerator::class, $urlGenerator);
$this->app->instance('http.urlGenerator', $urlGenerator);
}
}
diff --git a/src/Middleware/ErrorHandler.php b/src/Middleware/ErrorHandler.php
new file mode 100644
index 00000000..a7c4cfe6
--- /dev/null
+++ b/src/Middleware/ErrorHandler.php
@@ -0,0 +1,80 @@
+<?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;
+use Twig_LoaderInterface as TwigLoader;
+
+class ErrorHandler implements MiddlewareInterface
+{
+ /** @var TwigLoader */
+ protected $loader;
+
+ /** @var string */
+ protected $viewPrefix = 'errors/';
+
+ /**
+ * @param TwigLoader $loader
+ */
+ public function __construct(TwigLoader $loader)
+ {
+ $this->loader = $loader;
+ }
+
+ /**
+ * Handles any error messages
+ *
+ * Should be added at the beginning
+ *
+ * @param ServerRequestInterface $request
+ * @param RequestHandlerInterface $handler
+ * @return ResponseInterface
+ */
+ public function process(
+ ServerRequestInterface $request,
+ RequestHandlerInterface $handler
+ ): ResponseInterface {
+ $response = $handler->handle($request);
+
+ $statusCode = $response->getStatusCode();
+ if ($statusCode < 400 || !$response instanceof Response) {
+ return $response;
+ }
+
+ $view = $this->selectView($statusCode);
+
+ return $response->withView(
+ $this->viewPrefix . $view,
+ [
+ 'status' => $statusCode,
+ 'content' => $response->getContent(),
+ ],
+ $statusCode,
+ $response->getHeaders()
+ );
+ }
+
+ /**
+ * Select a view based on the given status code
+ *
+ * @param int $statusCode
+ * @return string
+ */
+ protected function selectView(int $statusCode): string
+ {
+ $hundreds = intdiv($statusCode, 100);
+
+ $viewsList = [$statusCode, $hundreds, $hundreds * 100];
+ foreach ($viewsList as $view) {
+ if ($this->loader->exists($this->viewPrefix . $view)) {
+ return $view;
+ }
+ }
+
+ return 'default';
+ }
+}
diff --git a/src/Middleware/LegacyMiddleware.php b/src/Middleware/LegacyMiddleware.php
index 276fb3ee..78132815 100644
--- a/src/Middleware/LegacyMiddleware.php
+++ b/src/Middleware/LegacyMiddleware.php
@@ -83,7 +83,7 @@ class LegacyMiddleware implements MiddlewareInterface
}
if (empty($title) and empty($content)) {
- $page = '404';
+ $page = 404;
$title = _('Page not found');
$content = _('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!');
}
@@ -277,29 +277,17 @@ class LegacyMiddleware implements MiddlewareInterface
$parameters['meetings'] = 1;
}
- $status = 200;
- if ($page == '404') {
- $status = 404;
- $content = info($content, true);
+ if (!empty($page) && is_int($page)) {
+ return response($content, (int)$page);
}
- return response(view(__DIR__ . '/../../templates/layout.html', [
- 'theme' => isset($user) ? $user['color'] : config('theme'),
+ return response(view('layouts/app', [
'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'),
+ 'atom_feed' => ($page == 'news' || $page == 'user_meetings') ? $parameters : [],
'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 />'
- ]), $status);
+ ]), 200);
}
}
diff --git a/src/Middleware/SetLocale.php b/src/Middleware/SetLocale.php
new file mode 100644
index 00000000..86fa0b7f
--- /dev/null
+++ b/src/Middleware/SetLocale.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Engelsystem\Middleware;
+
+use Engelsystem\Helpers\Translator;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use Symfony\Component\HttpFoundation\Session\Session;
+
+class SetLocale implements MiddlewareInterface
+{
+ /** @var Translator */
+ protected $translator;
+
+ /** @var Session */
+ protected $session;
+
+ /**
+ * @param Translator $translator
+ * @param Session $session
+ */
+ public function __construct(Translator $translator, Session $session)
+ {
+ $this->translator = $translator;
+ $this->session = $session;
+ }
+
+ /**
+ * Process an incoming server request and setting the locale if required
+ *
+ * @param ServerRequestInterface $request
+ * @param RequestHandlerInterface $handler
+ * @return ResponseInterface
+ */
+ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+ {
+ $query = $request->getQueryParams();
+ if (isset($query['set-locale']) && $this->translator->hasLocale($query['set-locale'])) {
+ $locale = $query['set-locale'];
+
+ $this->translator->setLocale($locale);
+ $this->session->set('locale', $locale);
+ }
+
+ return $handler->handle($request);
+ }
+}
diff --git a/src/Renderer/RendererServiceProvider.php b/src/Renderer/RendererServiceProvider.php
index 3e8d69bc..2e41837b 100644
--- a/src/Renderer/RendererServiceProvider.php
+++ b/src/Renderer/RendererServiceProvider.php
@@ -24,12 +24,14 @@ class RendererServiceProvider extends ServiceProvider
protected function registerRenderer()
{
$renderer = $this->app->make(Renderer::class);
+ $this->app->instance(Renderer::class, $renderer);
$this->app->instance('renderer', $renderer);
}
protected function registerHtmlEngine()
{
$htmlEngine = $this->app->make(HtmlEngine::class);
+ $this->app->instance(HtmlEngine::class, $htmlEngine);
$this->app->instance('renderer.htmlEngine', $htmlEngine);
$this->app->tag('renderer.htmlEngine', ['renderer.engine']);
}
diff --git a/src/Renderer/Twig/Extensions/Config.php b/src/Renderer/Twig/Extensions/Config.php
new file mode 100644
index 00000000..dbbe93e7
--- /dev/null
+++ b/src/Renderer/Twig/Extensions/Config.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Engelsystem\Renderer\Twig\Extensions;
+
+use Engelsystem\Config\Config as EngelsystemConfig;
+use Twig_Extension as TwigExtension;
+use Twig_Function as TwigFunction;
+
+class Config extends TwigExtension
+{
+ /** @var EngelsystemConfig */
+ protected $config;
+
+ /**
+ * @param EngelsystemConfig $config
+ */
+ public function __construct(EngelsystemConfig $config)
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * @return TwigFunction[]
+ */
+ public function getFunctions()
+ {
+ return [
+ new TwigFunction('config', [$this->config, 'get']),
+ ];
+ }
+}
diff --git a/src/Renderer/Twig/Extensions/Globals.php b/src/Renderer/Twig/Extensions/Globals.php
new file mode 100644
index 00000000..f9bffbc8
--- /dev/null
+++ b/src/Renderer/Twig/Extensions/Globals.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Engelsystem\Renderer\Twig\Extensions;
+
+use Twig_Extension as TwigExtension;
+use Twig_Extension_GlobalsInterface as GlobalsInterface;
+
+class Globals extends TwigExtension implements GlobalsInterface
+{
+ /**
+ * Returns a list of global variables to add to the existing list.
+ *
+ * @return array An array of global variables
+ */
+ public function getGlobals()
+ {
+ global $user;
+
+ return [
+ 'user' => isset($user) ? $user : [],
+ ];
+ }
+}
diff --git a/src/Renderer/Twig/Extensions/Session.php b/src/Renderer/Twig/Extensions/Session.php
new file mode 100644
index 00000000..4690f701
--- /dev/null
+++ b/src/Renderer/Twig/Extensions/Session.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Engelsystem\Renderer\Twig\Extensions;
+
+use Symfony\Component\HttpFoundation\Session\Session as SymfonySession;
+use Twig_Extension as TwigExtension;
+use Twig_Function as TwigFunction;
+
+class Session extends TwigExtension
+{
+ /** @var SymfonySession */
+ protected $session;
+
+ /**
+ * @param SymfonySession $session
+ */
+ public function __construct(SymfonySession $session)
+ {
+ $this->session = $session;
+ }
+
+ /**
+ * @return TwigFunction[]
+ */
+ public function getFunctions()
+ {
+ return [
+ new TwigFunction('session_get', [$this->session, 'get']),
+ ];
+ }
+}
diff --git a/src/Renderer/Twig/Extensions/Translation.php b/src/Renderer/Twig/Extensions/Translation.php
new file mode 100644
index 00000000..63f9800e
--- /dev/null
+++ b/src/Renderer/Twig/Extensions/Translation.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Engelsystem\Renderer\Twig\Extensions;
+
+use Engelsystem\Helpers\Translator;
+use Twig_Extension as TwigExtension;
+use Twig_Extensions_TokenParser_Trans as TranslationTokenParser;
+use Twig_Filter as TwigFilter;
+use Twig_Function as TwigFunction;
+use Twig_TokenParserInterface as TwigTokenParser;
+
+class Translation extends TwigExtension
+{
+ /** @var Translator */
+ protected $translator;
+
+ /** @var TranslationTokenParser */
+ protected $tokenParser;
+
+ /**
+ * @param Translator $translator
+ * @param TranslationTokenParser $tokenParser
+ */
+ public function __construct(Translator $translator, TranslationTokenParser $tokenParser)
+ {
+ $this->translator = $translator;
+ $this->tokenParser = $tokenParser;
+ }
+
+ /**
+ * @return array
+ */
+ public function getFilters()
+ {
+ return [
+ new TwigFilter('trans', [$this->translator, 'translate']),
+ ];
+ }
+
+ /**
+ * @return TwigFunction[]
+ */
+ public function getFunctions()
+ {
+ return [
+ new TwigFunction('__', [$this->translator, 'translate']),
+ ];
+ }
+
+ /**
+ * @return TwigTokenParser[]
+ */
+ public function getTokenParsers()
+ {
+ return [$this->tokenParser];
+ }
+}
diff --git a/src/Renderer/Twig/Extensions/Url.php b/src/Renderer/Twig/Extensions/Url.php
new file mode 100644
index 00000000..62e59782
--- /dev/null
+++ b/src/Renderer/Twig/Extensions/Url.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Engelsystem\Renderer\Twig\Extensions;
+
+use Engelsystem\Http\UrlGenerator;
+use Twig_Extension as TwigExtension;
+use Twig_Function as TwigFunction;
+
+class Url extends TwigExtension
+{
+ /** @var UrlGenerator */
+ protected $urlGenerator;
+
+ /**
+ * @param UrlGenerator $urlGenerator
+ */
+ public function __construct(UrlGenerator $urlGenerator)
+ {
+ $this->urlGenerator = $urlGenerator;
+ }
+
+ /**
+ * @return TwigFunction[]
+ */
+ public function getFunctions()
+ {
+ return [
+ new TwigFunction('url', [$this, 'getUrl']),
+ ];
+ }
+
+ /**
+ * @param string $path
+ * @param array $parameters
+ * @return UrlGenerator|string
+ */
+ public function getUrl($path, $parameters = [])
+ {
+ $path = str_replace('_', '-', $path);
+
+ return $this->urlGenerator->to($path, $parameters);
+ }
+}
diff --git a/src/Renderer/TwigEngine.php b/src/Renderer/TwigEngine.php
new file mode 100644
index 00000000..55a2e299
--- /dev/null
+++ b/src/Renderer/TwigEngine.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Engelsystem\Renderer;
+
+use Twig_Environment as Twig;
+use Twig_Error_Loader as LoaderError;
+use Twig_Error_Runtime as RuntimeError;
+use Twig_Error_Syntax as SyntaxError;
+
+class TwigEngine implements EngineInterface
+{
+ /** @var Twig */
+ protected $twig;
+
+ public function __construct(Twig $twig)
+ {
+ $this->twig = $twig;
+ }
+
+ /**
+ * Render a twig template
+ *
+ * @param string $path
+ * @param array $data
+ * @return string
+ * @throws LoaderError|RuntimeError|SyntaxError
+ */
+ public function get($path, $data = [])
+ {
+ return $this->twig->render($path, $data);
+ }
+
+ /**
+ * @param string $path
+ * @return bool
+ */
+ public function canRender($path)
+ {
+ return $this->twig->getLoader()->exists($path);
+ }
+}
diff --git a/src/Renderer/TwigLoader.php b/src/Renderer/TwigLoader.php
new file mode 100644
index 00000000..154e6dbb
--- /dev/null
+++ b/src/Renderer/TwigLoader.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Engelsystem\Renderer;
+
+use Twig_Error_Loader;
+use Twig_Loader_Filesystem as FilesystemLoader;
+
+class TwigLoader extends FilesystemLoader
+{
+ /**
+ * @param string $name
+ * @param bool $throw
+ * @return false|string
+ * @throws Twig_Error_Loader
+ */
+ public function findTemplate($name, $throw = true)
+ {
+ $extension = '.twig';
+ $extensionLength = strlen($extension);
+ if (substr($name, -$extensionLength, $extensionLength) !== $extension) {
+ $name .= $extension;
+ }
+
+ return parent::findTemplate($name, $throw);
+ }
+}
diff --git a/src/Renderer/TwigServiceProvider.php b/src/Renderer/TwigServiceProvider.php
new file mode 100644
index 00000000..0f453989
--- /dev/null
+++ b/src/Renderer/TwigServiceProvider.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Engelsystem\Renderer;
+
+use Engelsystem\Container\ServiceProvider;
+use Engelsystem\Renderer\Twig\Extensions\Config;
+use Engelsystem\Renderer\Twig\Extensions\Globals;
+use Engelsystem\Renderer\Twig\Extensions\Session;
+use Engelsystem\Renderer\Twig\Extensions\Translation;
+use Engelsystem\Renderer\Twig\Extensions\Url;
+use Twig_Environment as Twig;
+use Twig_LoaderInterface as TwigLoaderInterface;
+
+class TwigServiceProvider extends ServiceProvider
+{
+ /** @var array */
+ protected $extensions = [
+ 'config' => Config::class,
+ 'globals' => Globals::class,
+ 'session' => Session::class,
+ 'url' => Url::class,
+ 'translation' => Translation::class,
+ ];
+
+ public function register()
+ {
+ $this->registerTwigEngine();
+
+ foreach ($this->extensions as $alias => $class) {
+ $this->registerTwigExtensions($class, $alias);
+ }
+ }
+
+ public function boot()
+ {
+ /** @var Twig $renderer */
+ $renderer = $this->app->get('twig.environment');
+
+ foreach ($this->app->tagged('twig.extension') as $extension) {
+ $renderer->addExtension($extension);
+ }
+ }
+
+ protected function registerTwigEngine()
+ {
+ $viewsPath = $this->app->get('path.views');
+
+ $twigLoader = $this->app->make(TwigLoader::class, ['paths' => $viewsPath]);
+ $this->app->instance(TwigLoader::class, $twigLoader);
+ $this->app->instance(TwigLoaderInterface::class, $twigLoader);
+ $this->app->instance('twig.loader', $twigLoader);
+
+ $twig = $this->app->make(Twig::class);
+ $this->app->instance(Twig::class, $twig);
+ $this->app->instance('twig.environment', $twig);
+
+ $twigEngine = $this->app->make(TwigEngine::class);
+ $this->app->instance('renderer.twigEngine', $twigEngine);
+ $this->app->tag('renderer.twigEngine', ['renderer.engine']);
+ }
+
+ /**
+ * @param string $class
+ * @param string $alias
+ */
+ protected function registerTwigExtensions($class, $alias)
+ {
+ $alias = 'twig.extension.' . $alias;
+
+ $extension = $this->app->make($class);
+
+ $this->app->instance($class, $extension);
+ $this->app->instance($alias, $extension);
+
+ $this->app->tag($alias, ['twig.extension']);
+ }
+}
diff --git a/src/helpers.php b/src/helpers.php
index 95571a40..84f26dfa 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -3,10 +3,11 @@
use Engelsystem\Application;
use Engelsystem\Config\Config;
+use Engelsystem\Helpers\Translator;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
-use Engelsystem\Renderer\Renderer;
use Engelsystem\Http\UrlGenerator;
+use Engelsystem\Renderer\Renderer;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
/**
@@ -119,6 +120,40 @@ function session($key = null, $default = null)
}
/**
+ * Translate the given message
+ *
+ * @param string $key
+ * @param array $replace
+ * @return string|Translator
+ */
+function trans($key = null, $replace = [])
+{
+ /** @var Translator $translator */
+ $translator = app('translator');
+
+ if (is_null($key)) {
+ return $translator;
+ }
+
+ return $translator->translate($key, $replace);
+}
+
+/**
+ * Translate the given message
+ *
+ * @param string $key
+ * @param array $replace
+ * @return string
+ */
+function __($key, $replace = [])
+{
+ /** @var Translator $translator */
+ $translator = app('translator');
+
+ return $translator->translate($key, $replace);
+}
+
+/**
* @param string $path
* @param array $parameters
* @return UrlGeneratorInterface|string
@@ -139,7 +174,7 @@ function url($path = null, $parameters = [])
* @param mixed[] $data
* @return Renderer|string
*/
-function view($template = null, $data = null)
+function view($template = null, $data = [])
{
$renderer = app('renderer');
diff --git a/templates/errors/default.twig b/templates/errors/default.twig
new file mode 100644
index 00000000..5fb8bcbd
--- /dev/null
+++ b/templates/errors/default.twig
@@ -0,0 +1,7 @@
+{% extends "layouts/app.twig" %}
+
+{% block title %}{% if status == 404 %}{{ __("Page not found") }}{% else %}Error {{ status }}{% endif %}{% endblock %}
+
+{% block content %}
+ <div class="alert alert-info">{{ content }}</div>
+{% endblock %}
diff --git a/templates/layout.html b/templates/layout.html
deleted file mode 100644
index 832bdcf3..00000000
--- a/templates/layout.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE html>
-<html lang="%locale%">
-<head>
- <title>%title% - Engelsystem</title>
- <meta charset="UTF-8"/>
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <link rel="stylesheet" type="text/css" href="%start_page_url%assets/theme%theme%.css"/>
- <script type="text/javascript" src="%start_page_url%assets/vendor.js"></script>
- %atom_link%
-</head>
-<body>
-<div class="navbar navbar-default navbar-fixed-top">
- <div class="container-fluid">
- <div class="navbar-header">
- <button type="button" class="navbar-toggle collapsed"
- data-toggle="collapse" data-target="#navbar-collapse-1">
- <span class="sr-only">Toggle navigation</span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- </button>
- <a class="navbar-brand" href="%start_page_url%">
- <span class="icon-icon_angel"></span> <strong class="visible-lg-inline">ENGELSYSTEM</strong>
- </a>
- </div>
- <div class="collapse navbar-collapse" id="navbar-collapse-1">%menu% %header_toolbar%</div>
- </div>
-</div>
-<div class="container-fluid">
- <div class="row">%content%</div>
- <div class="row" id="footer">
- <div class="col-md-12">
- <hr/>
- <div class="text-center footer" style="margin-bottom: 10px;">
- %event_info%
- <a href="%faq_url%">FAQ</a>
- · <a href="%contact_email%"><span class="glyphicon glyphicon-envelope"></span> Contact</a>
- · <a href="https://github.com/engelsystem/engelsystem/issues">Bugs / Features</a>
- · <a href="https://github.com/engelsystem/engelsystem/">Development Platform</a>
- · <a href="%credits_url%">Credits</a>
- </div>
- </div>
- </div>
-</div>
-</body>
-</html>
diff --git a/templates/layouts/app.twig b/templates/layouts/app.twig
new file mode 100644
index 00000000..42d5610c
--- /dev/null
+++ b/templates/layouts/app.twig
@@ -0,0 +1,87 @@
+{% set theme = user.color|default(config('theme')) %}
+<!DOCTYPE html>
+<html>
+<head>
+ {% block head %}
+ <title>{% block title %}{{ title }}{% endblock %} - Engelsystem</title>
+ <meta charset="UTF-8"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="stylesheet" type="text/css" href="css/theme{{ theme }}.css"/>
+ <link rel="stylesheet" type="text/css" href="vendor/icomoon/style.css"/>
+ <link rel="stylesheet" type="text/css" href="vendor/bootstrap-datepicker-1.7.1/css/bootstrap-datepicker3.min.css"/>
+ <script type="text/javascript" src="vendor/jquery-2.1.1.min.js"></script>
+ <script type="text/javascript" src="vendor/jquery-ui.min.js"></script>
+ {% if atom_feed -%}
+ <link href="{{ url('atom', atom_feed) }}" type="application/atom+xml" rel="alternate" title="Atom Feed">
+ {% endif %}
+ {% endblock %}
+</head>
+<body>
+
+{% block body %}
+ <div class="navbar navbar-default navbar-fixed-top">
+ {% block header %}
+ <div class="container-fluid">
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle collapsed"
+ data-toggle="collapse" data-target="#navbar-collapse-1">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="{{ url('/') }}">
+ <span class="icon-icon_angel"></span> <strong class="visible-lg-inline">ENGELSYSTEM</strong>
+ </a>
+ </div>
+
+ {% block navbar %}
+ <div class="collapse navbar-collapse" id="navbar-collapse-1">
+ {{ menu|raw }}
+ {{ header_toolbar|raw }}
+ </div>
+ {% endblock %}
+ </div>
+ {% endblock %}
+ </div>
+
+ <div class="container-fluid">
+ <div class="row">{% block content %}{{ content|raw }}{% endblock %}</div>
+ <div class="row" id="footer">
+ {% block footer %}
+ <div class="col-md-12">
+ <hr/>
+ <div class="text-center footer" style="margin-bottom: 10px;">
+ {% block eventinfo %}
+ {{ event_info|raw }}
+ {% endblock %}
+ <a href="{{ config('faq_url') }}">FAQ</a>
+ · <a href="{{ config('contact_email') }}">
+ <span class="glyphicon glyphicon-envelope"></span>Contact
+ </a>
+ · <a href="https://github.com/engelsystem/engelsystem/issues">Bugs / Features</a>
+ · <a href="https://github.com/engelsystem/engelsystem/">Development Platform</a>
+ · <a href="{{ url('credits') }}">Credits</a>
+ </div>
+ </div>
+ {% endblock %}
+ </div>
+ </div>
+
+ <script type="text/javascript" src="vendor/bootstrap/js/bootstrap.min.js"></script>
+ <script type="text/javascript" src="vendor/bootstrap-datepicker-1.7.1/js/bootstrap-datepicker.min.js"></script>
+ <script type="text/javascript" src="vendor/bootstrap-datepicker-1.7.1/locales/bootstrap-datepicker.de.min.js"></script>
+ <script type="text/javascript" src="vendor/Chart.min.js"></script>
+ <script type="text/javascript" src="js/forms.js"></script>
+ <script type="text/javascript" src="vendor/moment-with-locales.min.js"></script>
+ <script type="text/javascript">
+ $(function () {
+ moment.locale("{{ session_get('locale')|escape('js') }}");
+ });
+ </script>
+ <script type="text/javascript" src="js/moment-countdown.js"></script>
+ <script type="text/javascript" src="js/sticky-headers.js"></script>
+{% endblock %}
+
+</body>
+</html>
diff --git a/templates/maintenance.html b/templates/layouts/maintenance.html
index f7ab5772..f7ab5772 100644
--- a/templates/maintenance.html
+++ b/templates/layouts/maintenance.html
diff --git a/templates/guest_credits.html b/templates/pages/credits.html
index db7fac57..4e247113 100644
--- a/templates/guest_credits.html
+++ b/templates/pages/credits.html
@@ -6,15 +6,15 @@
<p>
The original system was written by <a href="https://github.com/cookieBerlin/engelsystem">cookie</a>.
It was then completely rewritten and enhanced by
- <a href="http://notrademark.de/">msquare</a> (maintainer),
- <a href="http://myigel.name/">MyIgel</a>,
- <a href="http://mortzu.de/">mortzu</a>,
- <a href="http://jplitza.de/">jplitza</a> and
- gnomus.
+ <a href="https://notrademark.de">msquare</a> (maintainer),
+ <a href="https://myigel.name">MyIgel</a>,
+ <a href="https://mortzu.de">mortzu</a>,
+ <a href="https://jplitza.de">jplitza</a> and
+ <a href="https://github.com/gnomus">gnomus</a>.
</p>
<p>
- Please look at the <a href="https://github.com/engelsystem/engelsystem/graphs/contributors">contributor
- list on github</a> for a more complete version.
+ Please look at the <a href="https://github.com/engelsystem/engelsystem/graphs/contributors">
+ contributor list on github</a> for a more complete version.
</p>
</div>
<div class="col-md-4">
@@ -22,8 +22,8 @@
<p>
Webspace, development platform and domain on <a href="https://engelsystem.de">engelsystem.de</a>
is currently provided by <a href="https://www.wybt.net/">would you buy this?</a> (ichdasich)
- and adminstrated by <a href="http://mortzu.de/">mortzu</a>,
- <a href="http://derf.homelinux.org/">derf</a> and ichdasich.
+ and adminstrated by <a href="https://mortzu.de">mortzu</a>,
+ <a href="http://derf.homelinux.org">derf</a> and ichdasich.
</p>
</div>
<div class="col-md-4">
diff --git a/templates/user_shifts.html b/templates/pages/user-shifts.html
index 2fdade29..2fdade29 100644
--- a/templates/user_shifts.html
+++ b/templates/pages/user-shifts.html
diff --git a/tests/Unit/ApplicationTest.php b/tests/Unit/ApplicationTest.php
index 866eb957..012226b2 100644
--- a/tests/Unit/ApplicationTest.php
+++ b/tests/Unit/ApplicationTest.php
@@ -48,6 +48,7 @@ class ApplicationTest extends TestCase
$this->assertTrue($app->has('path'));
$this->assertTrue($app->has('path.config'));
$this->assertTrue($app->has('path.lang'));
+ $this->assertTrue($app->has('path.views'));
$this->assertEquals(realpath('.'), $app->path());
$this->assertEquals(realpath('.') . '/config', $app->get('path.config'));
diff --git a/tests/Unit/Config/ConfigServiceProviderTest.php b/tests/Unit/Config/ConfigServiceProviderTest.php
index 998c0ba1..925854be 100644
--- a/tests/Unit/Config/ConfigServiceProviderTest.php
+++ b/tests/Unit/Config/ConfigServiceProviderTest.php
@@ -24,8 +24,13 @@ class ConfigServiceProviderTest extends ServiceProviderTest
Application::setInstance($app);
$this->setExpects($app, 'make', [Config::class], $config);
- $this->setExpects($app, 'instance', ['config', $config]);
$this->setExpects($app, 'get', ['path.config'], __DIR__ . '/../../../config', $this->atLeastOnce());
+ $app->expects($this->exactly(2))
+ ->method('instance')
+ ->withConsecutive(
+ [Config::class, $config],
+ ['config', $config]
+ );
$this->setExpects($config, 'set', null, null, $this->exactly(2));
$config->expects($this->exactly(3))
@@ -60,7 +65,12 @@ class ConfigServiceProviderTest extends ServiceProviderTest
Application::setInstance($app);
$this->setExpects($app, 'make', [Config::class], $config);
- $this->setExpects($app, 'instance', ['config', $config]);
+ $app->expects($this->exactly(2))
+ ->method('instance')
+ ->withConsecutive(
+ [Config::class, $config],
+ ['config', $config]
+ );
$this->setExpects($app, 'get', ['path.config'], __DIR__ . '/not_existing', $this->atLeastOnce());
$this->setExpects($config, 'set', null, null, $this->never());
diff --git a/tests/Unit/Helpers/TranslationServiceProviderTest.php b/tests/Unit/Helpers/TranslationServiceProviderTest.php
new file mode 100644
index 00000000..c5ba7386
--- /dev/null
+++ b/tests/Unit/Helpers/TranslationServiceProviderTest.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Helpers;
+
+use Engelsystem\Config\Config;
+use Engelsystem\Helpers\TranslationServiceProvider;
+use Engelsystem\Helpers\Translator;
+use Engelsystem\Test\Unit\ServiceProviderTest;
+use PHPUnit\Framework\MockObject\MockObject;
+use Symfony\Component\HttpFoundation\Session\Session;
+
+class TranslationServiceProviderTest extends ServiceProviderTest
+{
+ /**
+ * @covers \Engelsystem\Helpers\TranslationServiceProvider::register()
+ */
+ public function testRegister()
+ {
+ $app = $this->getApp(['make', 'instance', 'get']);
+ /** @var Config|MockObject $config */
+ $config = $this->createMock(Config::class);
+ /** @var Session|MockObject $session */
+ $session = $this->createMock(Session::class);
+ /** @var Translator|MockObject $translator */
+ $translator = $this->createMock(Translator::class);
+
+ /** @var TranslationServiceProvider|MockObject $serviceProvider */
+ $serviceProvider = $this->getMockBuilder(TranslationServiceProvider::class)
+ ->setConstructorArgs([$app])
+ ->setMethods(['initGettext', 'setLocale'])
+ ->getMock();
+
+ $serviceProvider->expects($this->once())
+ ->method('initGettext');
+
+ $app->expects($this->exactly(2))
+ ->method('get')
+ ->withConsecutive(['config'], ['session'])
+ ->willReturnOnConsecutiveCalls($config, $session);
+
+ $defaultLocale = 'fo_OO';
+ $locale = 'te_ST.WTF-9';
+ $locales = ['fo_OO' => 'Foo', 'fo_OO.BAR' => 'Foo (Bar)', 'te_ST.WTF-9' => 'WTF\'s Testing?'];
+ $config->expects($this->exactly(2))
+ ->method('get')
+ ->withConsecutive(
+ ['locales'],
+ ['default_locale']
+ )
+ ->willReturnOnConsecutiveCalls(
+ $locales,
+ $defaultLocale
+ );
+
+ $session->expects($this->once())
+ ->method('get')
+ ->with('locale', $defaultLocale)
+ ->willReturn($locale);
+ $session->expects($this->once())
+ ->method('set')
+ ->with('locale', $locale);
+
+ $app->expects($this->once())
+ ->method('make')
+ ->with(
+ Translator::class,
+ [
+ 'locale' => $locale,
+ 'locales' => $locales,
+ 'localeChangeCallback' => [$serviceProvider, 'setLocale']
+ ]
+ )
+ ->willReturn($translator);
+
+ $app->expects($this->exactly(2))
+ ->method('instance')
+ ->withConsecutive(
+ [Translator::class, $translator],
+ ['translator', $translator]
+ );
+
+ $serviceProvider->register();
+ }
+}
+
+
diff --git a/tests/Unit/Helpers/TranslatorTest.php b/tests/Unit/Helpers/TranslatorTest.php
new file mode 100644
index 00000000..396d2b65
--- /dev/null
+++ b/tests/Unit/Helpers/TranslatorTest.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Helpers;
+
+use Engelsystem\Helpers\Translator;
+use Engelsystem\Test\Unit\ServiceProviderTest;
+use PHPUnit\Framework\MockObject\MockObject;
+use stdClass;
+
+class TranslatorTest extends ServiceProviderTest
+{
+ /**
+ * @covers \Engelsystem\Helpers\Translator::__construct()
+ * @covers \Engelsystem\Helpers\Translator::setLocale()
+ * @covers \Engelsystem\Helpers\Translator::setLocales()
+ * @covers \Engelsystem\Helpers\Translator::getLocale()
+ * @covers \Engelsystem\Helpers\Translator::getLocales()
+ * @covers \Engelsystem\Helpers\Translator::hasLocale()
+ */
+ public function testInit()
+ {
+ $locales = ['te_ST.ER-01' => 'Tests', 'fo_OO' => 'SomeFOO'];
+ $locale = 'te_ST.ER-01';
+
+ /** @var callable|MockObject $callable */
+ $callable = $this->getMockBuilder(stdClass::class)
+ ->setMethods(['__invoke'])
+ ->getMock();
+ $callable->expects($this->exactly(2))
+ ->method('__invoke')
+ ->withConsecutive(['te_ST.ER-01'], ['fo_OO']);
+
+ $translator = new Translator($locale, $locales, $callable);
+
+ $this->assertEquals($locales, $translator->getLocales());
+ $this->assertEquals($locale, $translator->getLocale());
+
+ $translator->setLocale('fo_OO');
+ $this->assertEquals('fo_OO', $translator->getLocale());
+
+ $newLocales = ['lo_RM' => 'Lorem', 'ip_SU-M' => 'Ipsum'];
+ $translator->setLocales($newLocales);
+ $this->assertEquals($newLocales, $translator->getLocales());
+
+ $this->assertTrue($translator->hasLocale('ip_SU-M'));
+ $this->assertFalse($translator->hasLocale('te_ST.ER-01'));
+ }
+
+ /**
+ * @covers \Engelsystem\Helpers\Translator::translate()
+ */
+ public function testTranslate()
+ {
+ /** @var Translator|MockObject $translator */
+ $translator = $this->getMockBuilder(Translator::class)
+ ->setConstructorArgs(['de_DE.UTF-8', ['de_DE.UTF-8' => 'Deutsch']])
+ ->setMethods(['translateGettext'])
+ ->getMock();
+ $translator->expects($this->once())
+ ->method('translateGettext')
+ ->with('My favourite number is %u!')
+ ->willReturn('Meine Lieblingszahl ist die %u!');
+
+ $return = $translator->translate('My favourite number is %u!', [3]);
+ $this->assertEquals('Meine Lieblingszahl ist die 3!', $return);
+ }
+}
+
+
diff --git a/tests/Unit/HelpersTest.php b/tests/Unit/HelpersTest.php
index fb9e6f00..b9cedd30 100644
--- a/tests/Unit/HelpersTest.php
+++ b/tests/Unit/HelpersTest.php
@@ -5,10 +5,10 @@ namespace Engelsystem\Test\Unit;
use Engelsystem\Application;
use Engelsystem\Config\Config;
use Engelsystem\Container\Container;
+use Engelsystem\Helpers\Translator;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Renderer\Renderer;
-use Engelsystem\Http\UrlGenerator;
use Engelsystem\Http\UrlGeneratorInterface;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
@@ -196,6 +196,29 @@ class HelpersTest extends TestCase
}
/**
+ * @covers \__
+ * @covers \trans
+ */
+ public function testTrans()
+ {
+ /** @var Translator|MockObject $translator */
+ $translator = $this->getMockBuilder(Translator::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->getAppMock('translator', $translator);
+
+ $translator->expects($this->exactly(2))
+ ->method('translate')
+ ->with('Lorem %s Ipsum', ['foo'])
+ ->willReturn('Lorem foo Ipsum');
+
+ $this->assertEquals($translator, trans());
+ $this->assertEquals('Lorem foo Ipsum', trans('Lorem %s Ipsum', ['foo']));
+ $this->assertEquals('Lorem foo Ipsum', __('Lorem %s Ipsum', ['foo']));
+ }
+
+ /**
* @covers \url
*/
public function testUrl()
diff --git a/tests/Unit/Http/ResponseTest.php b/tests/Unit/Http/ResponseTest.php
index f6c24767..d7dc37c0 100644
--- a/tests/Unit/Http/ResponseTest.php
+++ b/tests/Unit/Http/ResponseTest.php
@@ -3,6 +3,8 @@
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Response;
+use Engelsystem\Renderer\Renderer;
+use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
@@ -46,4 +48,37 @@ class ResponseTest extends TestCase
$this->assertNotEquals($response, $newResponse);
$this->assertEquals('Lorem Ipsum?', $newResponse->getContent());
}
-}
+
+ /**
+ * @covers \Engelsystem\Http\Response::withView
+ */
+ public function testWithView()
+ {
+ /** @var REnderer|MockObject $renderer */
+ $renderer = $this->createMock(Renderer::class);
+
+ $renderer->expects($this->once())
+ ->method('render')
+ ->with('foo', ['lorem' => 'ipsum'])
+ ->willReturn('Foo ipsum!');
+
+ $response = new Response('', 200, [], $renderer);
+ $newResponse = $response->withView('foo', ['lorem' => 'ipsum'], 505, ['test' => 'er']);
+
+ $this->assertNotEquals($response, $newResponse);
+ $this->assertEquals('Foo ipsum!', $newResponse->getContent());
+ $this->assertEquals(505, $newResponse->getStatusCode());
+ $this->assertArraySubset(['test' => ['er']], $newResponse->getHeaders());
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Response::withView
+ */
+ public function testWithViewNoRenderer()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+
+ $response = new Response();
+ $response->withView('foo');
+ }
+} \ No newline at end of file
diff --git a/tests/Unit/Http/SessionServiceProviderTest.php b/tests/Unit/Http/SessionServiceProviderTest.php
index a78b4f72..d0125bc2 100644
--- a/tests/Unit/Http/SessionServiceProviderTest.php
+++ b/tests/Unit/Http/SessionServiceProviderTest.php
@@ -54,6 +54,7 @@ class SessionServiceProviderTest extends ServiceProviderTest
->method('instance')
->withConsecutive(
['session.storage', $sessionStorage],
+ [Session::class, $session],
['session', $session]
);
@@ -88,10 +89,11 @@ class SessionServiceProviderTest extends ServiceProviderTest
$sessionStorage,
$session
);
- $app->expects($this->exactly(2))
+ $app->expects($this->exactly(3))
->method('instance')
->withConsecutive(
['session.storage', $sessionStorage],
+ [Session::class, $session],
['session', $session]
);
diff --git a/tests/Unit/Http/UrlGeneratorServiceProviderTest.php b/tests/Unit/Http/UrlGeneratorServiceProviderTest.php
index 874268b0..720af631 100644
--- a/tests/Unit/Http/UrlGeneratorServiceProviderTest.php
+++ b/tests/Unit/Http/UrlGeneratorServiceProviderTest.php
@@ -21,7 +21,12 @@ class UrlGeneratorServiceProviderTest extends ServiceProviderTest
$app = $this->getApp();
$this->setExpects($app, 'make', [UrlGenerator::class], $urlGenerator);
- $this->setExpects($app, 'instance', ['http.urlGenerator', $urlGenerator]);
+ $app->expects($this->exactly(2))
+ ->method('instance')
+ ->withConsecutive(
+ [UrlGenerator::class, $urlGenerator],
+ ['http.urlGenerator', $urlGenerator]
+ );
$serviceProvider = new UrlGeneratorServiceProvider($app);
$serviceProvider->register();
diff --git a/tests/Unit/Middleware/ErrorHandlerTest.php b/tests/Unit/Middleware/ErrorHandlerTest.php
new file mode 100644
index 00000000..abf9c52f
--- /dev/null
+++ b/tests/Unit/Middleware/ErrorHandlerTest.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Middleware;
+
+use Engelsystem\Http\Response;
+use Engelsystem\Middleware\ErrorHandler;
+use Engelsystem\Test\Unit\Middleware\Stub\ReturnResponseMiddlewareHandler;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Twig_LoaderInterface as TwigLoader;
+
+class ErrorHandlerTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Middleware\ErrorHandler::__construct
+ * @covers \Engelsystem\Middleware\ErrorHandler::process
+ * @covers \Engelsystem\Middleware\ErrorHandler::selectView
+ */
+ public function testProcess()
+ {
+ /** @var TwigLoader|MockObject $twigLoader */
+ $twigLoader = $this->createMock(TwigLoader::class);
+ /** @var ServerRequestInterface|MockObject $request */
+ $request = $this->createMock(ServerRequestInterface::class);
+ /** @var ResponseInterface|MockObject $psrResponse */
+ $psrResponse = $this->getMockForAbstractClass(ResponseInterface::class);
+ $returnResponseHandler = new ReturnResponseMiddlewareHandler($psrResponse);
+
+ $psrResponse->expects($this->once())
+ ->method('getStatusCode')
+ ->willReturn(505);
+
+ $errorHandler = new ErrorHandler($twigLoader);
+
+ $return = $errorHandler->process($request, $returnResponseHandler);
+ $this->assertEquals($psrResponse, $return, 'Plain PSR-7 Response should be passed directly');
+
+ /** @var Response|MockObject $response */
+ $response = $this->createMock(Response::class);
+
+ $response->expects($this->exactly(3))
+ ->method('getStatusCode')
+ ->willReturnOnConsecutiveCalls(
+ 200,
+ 418,
+ 505
+ );
+
+ $returnResponseHandler->setResponse($response);
+ $return = $errorHandler->process($request, $returnResponseHandler);
+ $this->assertEquals($response, $return, 'Only Responses >= 400 should be processed');
+
+ $twigLoader->expects($this->exactly(4))
+ ->method('exists')
+ ->withConsecutive(
+ ['errors/418'],
+ ['errors/4'],
+ ['errors/400'],
+ ['errors/505']
+ )
+ ->willReturnOnConsecutiveCalls(
+ false,
+ false,
+ false,
+ true
+ );
+
+ $response->expects($this->exactly(2))
+ ->method('getContent')
+ ->willReturnOnConsecutiveCalls(
+ 'Teapot',
+ 'Internal Error!'
+ );
+
+ $response->expects($this->exactly(2))
+ ->method('withView')
+ ->withConsecutive(
+ ['errors/default', ['status' => 418, 'content' => 'Teapot'], 418],
+ ['errors/505', ['status' => 505, 'content' => 'Internal Error!'], 505]
+ )
+ ->willReturn($response);
+
+ $errorHandler->process($request, $returnResponseHandler);
+ $errorHandler->process($request, $returnResponseHandler);
+ }
+}
diff --git a/tests/Unit/Middleware/SetLocaleTest.php b/tests/Unit/Middleware/SetLocaleTest.php
new file mode 100644
index 00000000..c4e9d2a4
--- /dev/null
+++ b/tests/Unit/Middleware/SetLocaleTest.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Middleware;
+
+use Engelsystem\Helpers\Translator;
+use Engelsystem\Middleware\SetLocale;
+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;
+use Symfony\Component\HttpFoundation\Session\Session;
+
+class SetLocaleTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Middleware\SetLocale::__construct
+ * @covers \Engelsystem\Middleware\SetLocale::process
+ */
+ public function testRegister()
+ {
+ /** @var Translator|MockObject $translator */
+ $translator = $this->createMock(Translator::class);
+ /** @var Session|MockObject $session */
+ $session = $this->createMock(Session::class);
+ /** @var ServerRequestInterface|MockObject $request */
+ $request = $this->getMockForAbstractClass(ServerRequestInterface::class);
+ /** @var RequestHandlerInterface|MockObject $handler */
+ $handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
+ /** @var ResponseInterface|MockObject $response */
+ $response = $this->getMockForAbstractClass(ResponseInterface::class);
+
+ $locale = 'te_ST.UTF8';
+
+ $request->expects($this->exactly(3))
+ ->method('getQueryParams')
+ ->willReturnOnConsecutiveCalls(
+ [],
+ ['set-locale' => 'en_US.UTF8'],
+ ['set-locale' => $locale]
+ );
+
+ $translator->expects($this->exactly(2))
+ ->method('hasLocale')
+ ->withConsecutive(
+ ['en_US.UTF8'],
+ [$locale]
+ )
+ ->willReturnOnConsecutiveCalls(
+ false,
+ true
+ );
+ $translator->expects($this->once())
+ ->method('setLocale')
+ ->with($locale);
+
+ $session->expects($this->once())
+ ->method('set')
+ ->with('locale', $locale);
+
+ $handler->expects($this->exactly(3))
+ ->method('handle')
+ ->with($request)
+ ->willReturn($response);
+
+ $middleware = new SetLocale($translator, $session);
+ $middleware->process($request, $handler);
+ $middleware->process($request, $handler);
+ $middleware->process($request, $handler);
+ }
+}
diff --git a/tests/Unit/Middleware/Stub/ReturnResponseMiddlewareHandler.php b/tests/Unit/Middleware/Stub/ReturnResponseMiddlewareHandler.php
index 323e07b4..370187dd 100644
--- a/tests/Unit/Middleware/Stub/ReturnResponseMiddlewareHandler.php
+++ b/tests/Unit/Middleware/Stub/ReturnResponseMiddlewareHandler.php
@@ -27,4 +27,14 @@ class ReturnResponseMiddlewareHandler implements RequestHandlerInterface
{
return $this->response;
}
+
+ /**
+ * Set the response
+ *
+ * @param ResponseInterface $response
+ */
+ public function setResponse(ResponseInterface $response)
+ {
+ $this->response = $response;
+ }
}
diff --git a/tests/Unit/Renderer/RendererServiceProviderTest.php b/tests/Unit/Renderer/RendererServiceProviderTest.php
index 3826da7e..6cdf4363 100644
--- a/tests/Unit/Renderer/RendererServiceProviderTest.php
+++ b/tests/Unit/Renderer/RendererServiceProviderTest.php
@@ -37,10 +37,12 @@ class RendererServiceProviderTest extends ServiceProviderTest
$htmlEngine
);
- $app->expects($this->exactly(2))
+ $app->expects($this->exactly(4))
->method('instance')
->withConsecutive(
+ [Renderer::class, $renderer],
['renderer', $renderer],
+ [HtmlEngine::class, $htmlEngine],
['renderer.htmlEngine', $htmlEngine]
);
diff --git a/tests/Unit/Renderer/Twig/Extensions/ConfigTest.php b/tests/Unit/Renderer/Twig/Extensions/ConfigTest.php
new file mode 100644
index 00000000..6a9cb78a
--- /dev/null
+++ b/tests/Unit/Renderer/Twig/Extensions/ConfigTest.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Renderer\Twig\Extensions;
+
+use Engelsystem\Config\Config as EngelsystemConfig;
+use Engelsystem\Renderer\Twig\Extensions\Config;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class ConfigTest extends ExtensionTest
+{
+ /**
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Config::__construct
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Config::getFunctions
+ */
+ public function testGetFunctions()
+ {
+ /** @var EngelsystemConfig|MockObject $config */
+ $config = $this->createMock(EngelsystemConfig::class);
+
+ $extension = new Config($config);
+ $functions = $extension->getFunctions();
+
+ $this->assertExtensionExists('config', [$config, 'get'], $functions);
+ }
+}
diff --git a/tests/Unit/Renderer/Twig/Extensions/ExtensionTest.php b/tests/Unit/Renderer/Twig/Extensions/ExtensionTest.php
new file mode 100644
index 00000000..e1c5a378
--- /dev/null
+++ b/tests/Unit/Renderer/Twig/Extensions/ExtensionTest.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Renderer\Twig\Extensions;
+
+use PHPUnit\Framework\TestCase;
+use Twig_Function as TwigFunction;
+
+abstract class ExtensionTest extends TestCase
+{
+ /**
+ * Assert that a twig filter was registered
+ *
+ * @param string $name
+ * @param callable $callback
+ * @param TwigFunction[] $functions
+ */
+ protected function assertFilterExists($name, $callback, $functions)
+ {
+ foreach ($functions as $function) {
+ if ($function->getName() != $name) {
+ continue;
+ }
+
+ $this->assertEquals($callback, $function->getCallable());
+ return;
+ }
+
+ $this->fail(sprintf('Filter %s not found', $name));
+ }
+
+ /**
+ * Assert that a twig function was registered
+ *
+ * @param string $name
+ * @param callable $callback
+ * @param TwigFunction[] $functions
+ */
+ protected function assertExtensionExists($name, $callback, $functions)
+ {
+ foreach ($functions as $function) {
+ if ($function->getName() != $name) {
+ continue;
+ }
+
+ $this->assertEquals($callback, $function->getCallable());
+ return;
+ }
+
+ $this->fail(sprintf('Function %s not found', $name));
+ }
+
+ /**
+ * Assert that a global exists
+ *
+ * @param string $name
+ * @param mixed $value
+ * @param mixed[] $globals
+ */
+ protected function assertGlobalsExists($name, $value, $globals)
+ {
+ if (isset($globals[$name])) {
+ $this->assertArraySubset([$name => $value], $globals);
+
+ return;
+ }
+
+ $this->fail(sprintf('Global %s not found', $name));
+ }
+
+ /**
+ * Assert that a token parser was set
+ *
+ * @param $tokenParser
+ * @param $tokenParsers
+ */
+ protected function assertTokenParserExists($tokenParser, $tokenParsers)
+ {
+ $this->assertArraySubset(
+ [$tokenParser],
+ $tokenParsers,
+ sprintf('Token parser %s not found', get_class($tokenParser))
+ );
+ }
+}
diff --git a/tests/Unit/Renderer/Twig/Extensions/GlobalsTest.php b/tests/Unit/Renderer/Twig/Extensions/GlobalsTest.php
new file mode 100644
index 00000000..6cc3a4da
--- /dev/null
+++ b/tests/Unit/Renderer/Twig/Extensions/GlobalsTest.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Renderer\Twig\Extensions;
+
+use Engelsystem\Renderer\Twig\Extensions\Globals;
+
+class GlobalsTest extends ExtensionTest
+{
+ /**
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Globals::getGlobals
+ */
+ public function testGetGlobals()
+ {
+ $extension = new Globals();
+ $globals = $extension->getGlobals();
+
+ $this->assertGlobalsExists('user', [], $globals);
+
+ global $user;
+ $user['foo'] = 'bar';
+
+ $globals = $extension->getGlobals();
+ $this->assertGlobalsExists('user', ['foo' => 'bar'], $globals);
+ }
+}
diff --git a/tests/Unit/Renderer/Twig/Extensions/SessionTest.php b/tests/Unit/Renderer/Twig/Extensions/SessionTest.php
new file mode 100644
index 00000000..7ce4dc3a
--- /dev/null
+++ b/tests/Unit/Renderer/Twig/Extensions/SessionTest.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Renderer\Twig\Extensions;
+
+use Engelsystem\Renderer\Twig\Extensions\Session;
+use PHPUnit\Framework\MockObject\MockObject;
+use Symfony\Component\HttpFoundation\Session\Session as SymfonySession;
+
+class SessionTest extends ExtensionTest
+{
+ /**
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Session::__construct
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Session::getFunctions
+ */
+ public function testGetGlobals()
+ {
+ /** @var SymfonySession|MockObject $session */
+ $session = $this->createMock(SymfonySession::class);
+
+ $extension = new Session($session);
+ $functions = $extension->getFunctions();
+
+ $this->assertExtensionExists('session_get', [$session, 'get'], $functions);
+ }
+}
diff --git a/tests/Unit/Renderer/Twig/Extensions/TranslationTest.php b/tests/Unit/Renderer/Twig/Extensions/TranslationTest.php
new file mode 100644
index 00000000..f1548604
--- /dev/null
+++ b/tests/Unit/Renderer/Twig/Extensions/TranslationTest.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Renderer\Twig\Extensions;
+
+use Engelsystem\Helpers\Translator;
+use Engelsystem\Renderer\Twig\Extensions\Translation;
+use PHPUnit\Framework\MockObject\MockObject;
+use Twig_Extensions_TokenParser_Trans as TranslationTokenParser;
+
+class TranslationTest extends ExtensionTest
+{
+ /**
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Translation::__construct
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Translation::getFilters
+ */
+ public function testGeFilters()
+ {
+ /** @var Translator|MockObject $translator */
+ $translator = $this->createMock(Translator::class);
+ /** @var TranslationTokenParser|MockObject $parser */
+ $parser = $this->createMock(TranslationTokenParser::class);
+
+ $extension = new Translation($translator, $parser);
+ $filters = $extension->getFilters();
+
+ $this->assertExtensionExists('trans', [$translator, 'translate'], $filters);
+ }
+
+ /**
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Translation::getFunctions
+ */
+ public function testGetFunctions()
+ {
+ /** @var Translator|MockObject $translator */
+ $translator = $this->createMock(Translator::class);
+ /** @var TranslationTokenParser|MockObject $parser */
+ $parser = $this->createMock(TranslationTokenParser::class);
+
+ $extension = new Translation($translator, $parser);
+ $functions = $extension->getFunctions();
+
+ $this->assertExtensionExists('__', [$translator, 'translate'], $functions);
+ }
+
+ /**
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Translation::getTokenParsers
+ */
+ public function testGetTokenParsers()
+ {
+ /** @var Translator|MockObject $translator */
+ $translator = $this->createMock(Translator::class);
+ /** @var TranslationTokenParser|MockObject $parser */
+ $parser = $this->createMock(TranslationTokenParser::class);
+
+ $extension = new Translation($translator, $parser);
+ $tokenParsers = $extension->getTokenParsers();
+
+ $this->assertTokenParserExists($parser, $tokenParsers);
+ }
+}
diff --git a/tests/Unit/Renderer/Twig/Extensions/UrlTest.php b/tests/Unit/Renderer/Twig/Extensions/UrlTest.php
new file mode 100644
index 00000000..c7e40bea
--- /dev/null
+++ b/tests/Unit/Renderer/Twig/Extensions/UrlTest.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Renderer\Twig\Extensions;
+
+use Engelsystem\Http\UrlGenerator;
+use Engelsystem\Renderer\Twig\Extensions\Url;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class UrlTest extends ExtensionTest
+{
+ /**
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Url::__construct
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Url::getFunctions
+ */
+ public function testGetGlobals()
+ {
+ /** @var UrlGenerator|MockObject $urlGenerator */
+ $urlGenerator = $this->createMock(UrlGenerator::class);
+
+ $extension = new Url($urlGenerator);
+ $functions = $extension->getFunctions();
+
+ $this->assertExtensionExists('url', [$extension, 'getUrl'], $functions);
+ }
+
+ /**
+ * @return string[][]
+ */
+ public function getUrls()
+ {
+ return [
+ ['/', '/', 'http://foo.bar/'],
+ ['/foo', '/foo', 'http://foo.bar/foo'],
+ ['foo_bar', 'foo-bar', 'http://foo.bar/foo-bar'],
+ ['dolor', 'dolor', 'http://foo.bar/dolor?lorem_ipsum=dolor', ['lorem_ipsum' => 'dolor']],
+ ];
+ }
+
+ /**
+ * @dataProvider getUrls
+ *
+ * @param string $url
+ * @param string $return
+ * @param string $urlTo
+ * @param array $parameters
+ *
+ * @covers \Engelsystem\Renderer\Twig\Extensions\Url::getUrl
+ */
+ public function testGetUrl($url, $urlTo, $return, $parameters = [])
+ {
+ /** @var UrlGenerator|MockObject $urlGenerator */
+ $urlGenerator = $this->createMock(UrlGenerator::class);
+
+ $urlGenerator->expects($this->once())
+ ->method('to')
+ ->with($urlTo, $parameters)
+ ->willReturn($return);
+
+ $extension = new Url($urlGenerator);
+ $generatedUrl = $extension->getUrl($url, $parameters);
+
+ $this->assertEquals($return, $generatedUrl);
+ }
+}
diff --git a/tests/Unit/Renderer/TwigEngineTest.php b/tests/Unit/Renderer/TwigEngineTest.php
new file mode 100644
index 00000000..9d0618f1
--- /dev/null
+++ b/tests/Unit/Renderer/TwigEngineTest.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Renderer;
+
+use Engelsystem\Renderer\TwigEngine;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Twig_Environment as Twig;
+use Twig_LoaderInterface as LoaderInterface;
+
+class TwigEngineTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Renderer\TwigEngine::__construct
+ * @covers \Engelsystem\Renderer\TwigEngine::get
+ */
+ public function testGet()
+ {
+ /** @var Twig|MockObject $twig */
+ $twig = $this->createMock(Twig::class);
+
+ $path = 'foo.twig';
+ $data = ['lorem' => 'ipsum'];
+
+ $twig->expects($this->once())
+ ->method('render')
+ ->with($path, $data)
+ ->willReturn('LoremIpsum!');
+
+ $engine = new TwigEngine($twig);
+ $return = $engine->get($path, $data);
+ $this->assertEquals('LoremIpsum!', $return);
+ }
+
+
+ /**
+ * @covers \Engelsystem\Renderer\TwigEngine::canRender
+ */
+ public function testCanRender()
+ {
+ /** @var Twig|MockObject $twig */
+ $twig = $this->createMock(Twig::class);
+ /** @var LoaderInterface|MockObject $loader */
+ $loader = $this->getMockForAbstractClass(LoaderInterface::class);
+
+ $path = 'foo.twig';
+
+ $twig->expects($this->once())
+ ->method('getLoader')
+ ->willReturn($loader);
+ $loader->expects($this->once())
+ ->method('exists')
+ ->with($path)
+ ->willReturn(true);
+
+ $engine = new TwigEngine($twig);
+ $return = $engine->canRender($path);
+ $this->assertTrue($return);
+ }
+}
diff --git a/tests/Unit/Renderer/TwigLoaderTest.php b/tests/Unit/Renderer/TwigLoaderTest.php
new file mode 100644
index 00000000..e6867643
--- /dev/null
+++ b/tests/Unit/Renderer/TwigLoaderTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Renderer;
+
+use Engelsystem\Renderer\TwigLoader;
+use PHPUnit\Framework\TestCase;
+use ReflectionClass as Reflection;
+
+class TwigLoaderTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Renderer\TwigLoader::findTemplate
+ */
+ public function testFindTemplate()
+ {
+ $loader = new TwigLoader();
+
+ $reflection = new Reflection(get_class($loader));
+ $property = $reflection->getProperty('cache');
+ $property->setAccessible(true);
+
+ $realPath = __DIR__ . '/Stub/foo.twig';
+ $property->setValue($loader, ['Stub/foo.twig' => $realPath]);
+
+ $return = $loader->findTemplate('Stub/foo.twig');
+ $this->assertEquals($realPath, $return);
+
+ $return = $loader->findTemplate('Stub/foo');
+ $this->assertEquals($realPath, $return);
+ }
+}
diff --git a/tests/Unit/Renderer/TwigServiceProviderTest.php b/tests/Unit/Renderer/TwigServiceProviderTest.php
new file mode 100644
index 00000000..3cd0da4d
--- /dev/null
+++ b/tests/Unit/Renderer/TwigServiceProviderTest.php
@@ -0,0 +1,155 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Renderer;
+
+use Engelsystem\Renderer\TwigEngine;
+use Engelsystem\Renderer\TwigLoader;
+use Engelsystem\Renderer\TwigServiceProvider;
+use Engelsystem\Test\Unit\ServiceProviderTest;
+use PHPUnit\Framework\MockObject\MockObject;
+use ReflectionClass as Reflection;
+use stdClass;
+use Twig_Environment as Twig;
+use Twig_ExtensionInterface as ExtensionInterface;
+use Twig_LoaderInterface as TwigLoaderInterface;
+
+class TwigServiceProviderTest extends ServiceProviderTest
+{
+ /**
+ * @covers \Engelsystem\Renderer\TwigServiceProvider::register
+ * @covers \Engelsystem\Renderer\TwigServiceProvider::registerTwigExtensions
+ */
+ public function testRegister()
+ {
+ $app = $this->getApp(['make', 'instance', 'tag']);
+ $class = $this->createMock(stdClass::class);
+
+ $className = 'Foo\Bar\Class';
+ $classAlias = 'twig.extension.foo';
+
+ $app->expects($this->once())
+ ->method('make')
+ ->with('Foo\Bar\Class')
+ ->willReturn($class);
+
+ $app->expects($this->exactly(2))
+ ->method('instance')
+ ->withConsecutive(
+ [$className, $class],
+ [$classAlias, $class]
+ );
+
+ $app->expects($this->once())
+ ->method('tag')
+ ->with($classAlias, ['twig.extension']);
+
+ /** @var TwigServiceProvider|MockObject $serviceProvider */
+ $serviceProvider = $this->getMockBuilder(TwigServiceProvider::class)
+ ->setConstructorArgs([$app])
+ ->setMethods(['registerTwigEngine'])
+ ->getMock();
+ $serviceProvider->expects($this->once())
+ ->method('registerTwigEngine');
+ $this->setExtensionsTo($serviceProvider, ['foo' => 'Foo\Bar\Class']);
+
+ $serviceProvider->register();
+ }
+
+ /**
+ * @covers \Engelsystem\Renderer\TwigServiceProvider::boot
+ */
+ public function testBoot()
+ {
+ /** @var Twig|MockObject $twig */
+ $twig = $this->createMock(Twig::class);
+ /** @var ExtensionInterface|MockObject $firsExtension */
+ $firsExtension = $this->getMockForAbstractClass(ExtensionInterface::class);
+ /** @var ExtensionInterface|MockObject $secondExtension */
+ $secondExtension = $this->getMockForAbstractClass(ExtensionInterface::class);
+
+ $app = $this->getApp(['get', 'tagged']);
+
+ $app->expects($this->once())
+ ->method('get')
+ ->with('twig.environment')
+ ->willReturn($twig);
+ $app->expects($this->once())
+ ->method('tagged')
+ ->with('twig.extension')
+ ->willReturn([$firsExtension, $secondExtension]);
+
+ $twig->expects($this->exactly(2))
+ ->method('addExtension')
+ ->withConsecutive($firsExtension, $secondExtension);
+
+ $serviceProvider = new TwigServiceProvider($app);
+ $serviceProvider->boot();
+ }
+
+ /**
+ * @covers \Engelsystem\Renderer\TwigServiceProvider::registerTwigEngine
+ */
+ public function testRegisterTWigEngine()
+ {
+ /** @var TwigEngine|MockObject $htmlEngine */
+ $twigEngine = $this->createMock(TwigEngine::class);
+ /** @var TwigLoader|MockObject $twigLoader */
+ $twigLoader = $this->createMock(TwigLoader::class);
+ /** @var Twig|MockObject $twig */
+ $twig = $this->createMock(Twig::class);
+
+ $app = $this->getApp(['make', 'instance', 'tag', 'get']);
+
+ $viewsPath = __DIR__ . '/Stub';
+
+ $app->expects($this->exactly(3))
+ ->method('make')
+ ->withConsecutive(
+ [TwigLoader::class, ['paths' => $viewsPath]],
+ [Twig::class],
+ [TwigEngine::class]
+ )->willReturnOnConsecutiveCalls(
+ $twigLoader,
+ $twig,
+ $twigEngine
+ );
+
+ $app->expects($this->exactly(6))
+ ->method('instance')
+ ->withConsecutive(
+ [TwigLoader::class, $twigLoader],
+ [TwigLoaderInterface::class, $twigLoader],
+ ['twig.loader', $twigLoader],
+ [Twig::class, $twig],
+ ['twig.environment', $twig],
+ ['renderer.twigEngine', $twigEngine]
+ );
+
+ $app->expects($this->once())
+ ->method('get')
+ ->with('path.views')
+ ->willReturn($viewsPath);
+
+ $this->setExpects($app, 'tag', ['renderer.twigEngine', ['renderer.engine']]);
+
+ $serviceProvider = new TwigServiceProvider($app);
+ $this->setExtensionsTo($serviceProvider, []);
+
+ $serviceProvider->register();
+ }
+
+ /**
+ * @param TwigServiceProvider $serviceProvider
+ * @param array $extensions
+ * @throws \ReflectionException
+ */
+ protected function setExtensionsTo($serviceProvider, $extensions)
+ {
+ $reflection = new Reflection(get_class($serviceProvider));
+
+ $property = $reflection->getProperty('extensions');
+ $property->setAccessible(true);
+
+ $property->setValue($serviceProvider, $extensions);
+ }
+}