diff options
-rw-r--r-- | composer.json | 1 | ||||
-rw-r--r-- | config/app.php | 1 | ||||
-rw-r--r-- | includes/engelsystem.php | 2 | ||||
-rw-r--r-- | includes/pages/guest_credits.php | 2 | ||||
-rw-r--r-- | includes/pages/user_shifts.php | 2 | ||||
-rw-r--r-- | src/Application.php | 1 | ||||
-rw-r--r-- | src/Middleware/LegacyMiddleware.php | 2 | ||||
-rw-r--r-- | src/Renderer/TwigEngine.php | 41 | ||||
-rw-r--r-- | src/Renderer/TwigLoader.php | 26 | ||||
-rw-r--r-- | src/Renderer/TwigServiceProvider.php | 31 | ||||
-rw-r--r-- | src/helpers.php | 4 | ||||
-rw-r--r-- | templates/layout.html | 62 | ||||
-rw-r--r-- | templates/layouts/app.twig | 80 | ||||
-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.php | 1 | ||||
-rw-r--r-- | tests/Unit/Renderer/TwigEngineTest.php | 60 | ||||
-rw-r--r-- | tests/Unit/Renderer/TwigLoaderTest.php | 31 | ||||
-rw-r--r-- | tests/Unit/Renderer/TwigServiceProviderTest.php | 63 |
20 files changed, 351 insertions, 77 deletions
diff --git a/composer.json b/composer.json index f38bb972..0e6ee17d 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "symfony/http-foundation": "^3.3", "symfony/psr-http-message-bridge": "^1.0", "twbs/bootstrap": "^3.3", + "twig/twig": "^2.5", "zendframework/zend-diactoros": "^1.7" }, "require-dev": { diff --git a/config/app.php b/config/app.php index 9af35eb4..13d4d22b 100644 --- a/config/app.php +++ b/config/app.php @@ -10,6 +10,7 @@ return [ \Engelsystem\Config\ConfigServiceProvider::class, \Engelsystem\Http\UrlGeneratorServiceProvider::class, \Engelsystem\Renderer\RendererServiceProvider::class, + \Engelsystem\Renderer\TwigServiceProvider::class, \Engelsystem\Database\DatabaseServiceProvider::class, \Engelsystem\Http\RequestServiceProvider::class, \Engelsystem\Http\SessionServiceProvider::class, diff --git a/includes/engelsystem.php b/includes/engelsystem.php index f7d813c5..eb5b220a 100644 --- a/includes/engelsystem.php +++ b/includes/engelsystem.php @@ -16,7 +16,7 @@ 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(); } 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 a620d081..67f6785f 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/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/Middleware/LegacyMiddleware.php b/src/Middleware/LegacyMiddleware.php index 276fb3ee..37ae9331 100644 --- a/src/Middleware/LegacyMiddleware.php +++ b/src/Middleware/LegacyMiddleware.php @@ -283,7 +283,7 @@ class LegacyMiddleware implements MiddlewareInterface $content = info($content, true); } - return response(view(__DIR__ . '/../../templates/layout.html', [ + return response(view('layouts/app', [ 'theme' => isset($user) ? $user['color'] : config('theme'), 'title' => $title, 'atom_link' => ($page == 'news' || $page == 'user_meetings') 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..23810863 --- /dev/null +++ b/src/Renderer/TwigServiceProvider.php @@ -0,0 +1,31 @@ +<?php + +namespace Engelsystem\Renderer; + +use Engelsystem\Container\ServiceProvider; +use Twig_Environment as Twig; +use Twig_LoaderInterface as TwigLoaderInterface; + +class TwigServiceProvider extends ServiceProvider +{ + public function register() + { + $this->registerTwigEngine(); + } + + 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); + + $twig = $this->app->make(Twig::class); + $this->app->instance(Twig::class, $twig); + + $twigEngine = $this->app->make(TwigEngine::class); + $this->app->instance('renderer.twigEngine', $twigEngine); + $this->app->tag('renderer.twigEngine', ['renderer.engine']); + } +} diff --git a/src/helpers.php b/src/helpers.php index a90b2462..336f81fe 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -5,8 +5,8 @@ use Engelsystem\Application; use Engelsystem\Config\Config; 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; /** @@ -139,7 +139,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/layout.html b/templates/layout.html deleted file mode 100644 index 12a91086..00000000 --- a/templates/layout.html +++ /dev/null @@ -1,62 +0,0 @@ -<!DOCTYPE html> -<html> -<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="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> - %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> -<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("%locale%"); - }); -</script> -<script type="text/javascript" src="js/moment-countdown.js"></script> -<script type="text/javascript" src="js/sticky-headers.js"></script> -</body> -</html> diff --git a/templates/layouts/app.twig b/templates/layouts/app.twig new file mode 100644 index 00000000..6b6bd16f --- /dev/null +++ b/templates/layouts/app.twig @@ -0,0 +1,80 @@ +<!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> + {{ atom_link|raw }} + {% 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="{{ start_page_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="{{ 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> + {% 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("{{ 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 bd73bd74..bd73bd74 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 e137210c..e137210c 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/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..ede6fae4 --- /dev/null +++ b/tests/Unit/Renderer/TwigServiceProviderTest.php @@ -0,0 +1,63 @@ +<?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 Twig_Environment as Twig; +use Twig_LoaderInterface as TwigLoaderInterface; + +class TwigServiceProviderTest extends ServiceProviderTest +{ + /** + * @covers \Engelsystem\Renderer\TwigServiceProvider::register + * @covers \Engelsystem\Renderer\TwigServiceProvider::registerTwigEngine + */ + public function testRegister() + { + /** @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(4)) + ->method('instance') + ->withConsecutive( + [TwigLoader::class, $twigLoader], + [TwigLoaderInterface::class, $twigLoader], + [Twig::class, $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); + $serviceProvider->register(); + } +} |