From bb3d16d273bb3e4552e4869dd22cb2c2d81f5387 Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Sun, 26 Aug 2018 02:54:52 +0200 Subject: [PATCH 1/5] Added Twig template renderer, closes #338 --- composer.json | 1 + config/app.php | 1 + includes/engelsystem.php | 2 +- includes/pages/guest_credits.php | 2 +- includes/pages/user_shifts.php | 2 +- src/Application.php | 1 + src/Middleware/LegacyMiddleware.php | 2 +- src/Renderer/TwigEngine.php | 41 ++++++++++ src/Renderer/TwigLoader.php | 26 ++++++ src/Renderer/TwigServiceProvider.php | 31 +++++++ src/helpers.php | 4 +- templates/layout.html | 62 -------------- templates/layouts/app.twig | 80 +++++++++++++++++++ templates/{ => layouts}/maintenance.html | 0 .../credits.html} | 18 ++--- .../user-shifts.html} | 0 tests/Unit/ApplicationTest.php | 1 + tests/Unit/Renderer/TwigEngineTest.php | 60 ++++++++++++++ tests/Unit/Renderer/TwigLoaderTest.php | 31 +++++++ .../Unit/Renderer/TwigServiceProviderTest.php | 63 +++++++++++++++ 20 files changed, 351 insertions(+), 77 deletions(-) create mode 100644 src/Renderer/TwigEngine.php create mode 100644 src/Renderer/TwigLoader.php create mode 100644 src/Renderer/TwigServiceProvider.php delete mode 100644 templates/layout.html create mode 100644 templates/layouts/app.twig rename templates/{ => layouts}/maintenance.html (100%) rename templates/{guest_credits.html => pages/credits.html} (64%) rename templates/{user_shifts.html => pages/user-shifts.html} (100%) create mode 100644 tests/Unit/Renderer/TwigEngineTest.php create mode 100644 tests/Unit/Renderer/TwigLoaderTest.php create mode 100644 tests/Unit/Renderer/TwigServiceProviderTest.php 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 @@ +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 @@ +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 @@ - - - - %title% - Engelsystem - - - - - - - - %atom_link% - - - -
-
%content%
- -
- - - - - - - - - - - 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 @@ + + + + {% block head %} + {% block title %}{{ title }}{% endblock %} - Engelsystem + + + + + + + + {{ atom_link|raw }} + {% endblock %} + + + +{% block body %} + + +
+
{% block content %}{{ content|raw }}{% endblock %}
+ +
+ + + + + + + + + + +{% endblock %} + + + diff --git a/templates/maintenance.html b/templates/layouts/maintenance.html similarity index 100% rename from templates/maintenance.html rename to templates/layouts/maintenance.html diff --git a/templates/guest_credits.html b/templates/pages/credits.html similarity index 64% rename from templates/guest_credits.html rename to templates/pages/credits.html index db7fac57..4e247113 100644 --- a/templates/guest_credits.html +++ b/templates/pages/credits.html @@ -6,15 +6,15 @@

The original system was written by cookie. It was then completely rewritten and enhanced by - msquare (maintainer), - MyIgel, - mortzu, - jplitza and - gnomus. + msquare (maintainer), + MyIgel, + mortzu, + jplitza and + gnomus.

- Please look at the contributor - list on github for a more complete version. + Please look at the + contributor list on github for a more complete version.

@@ -22,8 +22,8 @@

Webspace, development platform and domain on engelsystem.de is currently provided by would you buy this? (ichdasich) - and adminstrated by mortzu, - derf and ichdasich. + and adminstrated by mortzu, + derf and ichdasich.

diff --git a/templates/user_shifts.html b/templates/pages/user-shifts.html similarity index 100% rename from templates/user_shifts.html rename to 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 @@ +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 @@ +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 @@ +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(); + } +} From df6360044b5c2396b2bee0dfa9e8d744bfa424d5 Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Sun, 26 Aug 2018 12:23:47 +0200 Subject: [PATCH 2/5] Added Twig template functions --- src/Config/ConfigServiceProvider.php | 1 + src/Http/SessionServiceProvider.php | 1 + src/Http/UrlGeneratorServiceProvider.php | 1 + src/Middleware/LegacyMiddleware.php | 12 +-- src/Renderer/Twig/Extensions/Config.php | 31 ++++++ src/Renderer/Twig/Extensions/Globals.php | 23 +++++ src/Renderer/Twig/Extensions/Session.php | 31 ++++++ src/Renderer/Twig/Extensions/Url.php | 43 +++++++++ src/Renderer/TwigServiceProvider.php | 44 +++++++++ templates/layouts/app.twig | 23 +++-- .../Unit/Config/ConfigServiceProviderTest.php | 7 +- .../Unit/Http/SessionServiceProviderTest.php | 4 +- .../Http/UrlGeneratorServiceProviderTest.php | 7 +- .../Renderer/Twig/Extensions/ConfigTest.php | 25 +++++ .../Twig/Extensions/ExtensionTest.php | 48 ++++++++++ .../Renderer/Twig/Extensions/GlobalsTest.php | 25 +++++ .../Renderer/Twig/Extensions/SessionTest.php | 25 +++++ .../Unit/Renderer/Twig/Extensions/UrlTest.php | 64 +++++++++++++ .../Unit/Renderer/TwigServiceProviderTest.php | 96 ++++++++++++++++++- 19 files changed, 487 insertions(+), 24 deletions(-) create mode 100644 src/Renderer/Twig/Extensions/Config.php create mode 100644 src/Renderer/Twig/Extensions/Globals.php create mode 100644 src/Renderer/Twig/Extensions/Session.php create mode 100644 src/Renderer/Twig/Extensions/Url.php create mode 100644 tests/Unit/Renderer/Twig/Extensions/ConfigTest.php create mode 100644 tests/Unit/Renderer/Twig/Extensions/ExtensionTest.php create mode 100644 tests/Unit/Renderer/Twig/Extensions/GlobalsTest.php create mode 100644 tests/Unit/Renderer/Twig/Extensions/SessionTest.php create mode 100644 tests/Unit/Renderer/Twig/Extensions/UrlTest.php diff --git a/src/Config/ConfigServiceProvider.php b/src/Config/ConfigServiceProvider.php index 01b648df..21cbbeb6 100644 --- a/src/Config/ConfigServiceProvider.php +++ b/src/Config/ConfigServiceProvider.php @@ -12,6 +12,7 @@ class ConfigServiceProvider extends ServiceProvider $configFile = config_path('config.php'); $config = $this->app->make(Config::class); + $this->app->instance(Config::class, $config); $this->app->instance('config', $config); $config->set(require $defaultConfigFile); 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/LegacyMiddleware.php b/src/Middleware/LegacyMiddleware.php index 37ae9331..bf849611 100644 --- a/src/Middleware/LegacyMiddleware.php +++ b/src/Middleware/LegacyMiddleware.php @@ -284,21 +284,11 @@ class LegacyMiddleware implements MiddlewareInterface } return response(view('layouts/app', [ - 'theme' => isset($user) ? $user['color'] : config('theme'), 'title' => $title, - 'atom_link' => ($page == 'news' || $page == 'user_meetings') - ? ' ' - : '', - 'start_page_url' => page_link_to('/'), - 'credits_url' => page_link_to('credits'), + '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) . '
' ]), $status); } 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 @@ +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 @@ + 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 @@ +session = $session; + } + + /** + * @return TwigFunction[] + */ + public function getFunctions() + { + return [ + new TwigFunction('session_get', [$this->session, 'get']), + ]; + } +} 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 @@ +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/TwigServiceProvider.php b/src/Renderer/TwigServiceProvider.php index 23810863..4a5b1de3 100644 --- a/src/Renderer/TwigServiceProvider.php +++ b/src/Renderer/TwigServiceProvider.php @@ -3,14 +3,40 @@ 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\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, + ]; + 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() @@ -20,12 +46,30 @@ class TwigServiceProvider extends ServiceProvider $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/templates/layouts/app.twig b/templates/layouts/app.twig index 6b6bd16f..42d5610c 100644 --- a/templates/layouts/app.twig +++ b/templates/layouts/app.twig @@ -1,3 +1,4 @@ +{% set theme = user.color|default(config('theme')) %} @@ -10,7 +11,9 @@ - {{ atom_link|raw }} + {% if atom_feed -%} + + {% endif %} {% endblock %} @@ -27,14 +30,16 @@ - + ENGELSYSTEM
{% block navbar %} - + {% endblock %} {% endblock %} @@ -50,11 +55,13 @@ {% block eventinfo %} {{ event_info|raw }} {% endblock %} - FAQ - · Contact + FAQ + · + Contact + · Bugs / Features · Development Platform - · Credits + · Credits {% endblock %} @@ -69,7 +76,7 @@ diff --git a/tests/Unit/Config/ConfigServiceProviderTest.php b/tests/Unit/Config/ConfigServiceProviderTest.php index c8be4b7d..2e37d0da 100644 --- a/tests/Unit/Config/ConfigServiceProviderTest.php +++ b/tests/Unit/Config/ConfigServiceProviderTest.php @@ -23,8 +23,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)); $this->setExpects($config, 'get', [null], []); 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 f3d5e018..787789fe 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/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 @@ +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..10f2e69a --- /dev/null +++ b/tests/Unit/Renderer/Twig/Extensions/ExtensionTest.php @@ -0,0 +1,48 @@ +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)); + } +} 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 @@ +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 @@ +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/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 @@ +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/TwigServiceProviderTest.php b/tests/Unit/Renderer/TwigServiceProviderTest.php index ede6fae4..3cd0da4d 100644 --- a/tests/Unit/Renderer/TwigServiceProviderTest.php +++ b/tests/Unit/Renderer/TwigServiceProviderTest.php @@ -7,16 +7,89 @@ 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::registerTwigEngine + * @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); @@ -41,12 +114,14 @@ class TwigServiceProviderTest extends ServiceProviderTest $twigEngine ); - $app->expects($this->exactly(4)) + $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] ); @@ -58,6 +133,23 @@ class TwigServiceProviderTest extends ServiceProviderTest $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); + } } From 427315195bdd379a0207fc9b2aaf69a5b5b86c79 Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Tue, 28 Aug 2018 22:23:59 +0200 Subject: [PATCH 3/5] Moved translation/internationalization to Helpers\Translator class --- config/app.php | 2 + includes/engelsystem.php | 6 - includes/helper/email_helper.php | 8 +- .../helper/internationalization_helper.php | 80 ------------- includes/includes.php | 1 - includes/sys_form.php | 5 +- includes/sys_menu.php | 26 ++++- src/Helpers/TranslationServiceProvider.php | 58 ++++++++++ src/Helpers/Translator.php | 105 ++++++++++++++++++ src/Middleware/SetLocale.php | 49 ++++++++ src/helpers.php | 35 ++++++ .../TranslationServiceProviderTest.php | 86 ++++++++++++++ tests/Unit/Helpers/TranslatorTest.php | 69 ++++++++++++ tests/Unit/HelpersTest.php | 26 ++++- tests/Unit/Middleware/SetLocaleTest.php | 71 ++++++++++++ 15 files changed, 534 insertions(+), 93 deletions(-) delete mode 100644 includes/helper/internationalization_helper.php create mode 100644 src/Helpers/TranslationServiceProvider.php create mode 100644 src/Helpers/Translator.php create mode 100644 src/Middleware/SetLocale.php create mode 100644 tests/Unit/Helpers/TranslationServiceProviderTest.php create mode 100644 tests/Unit/Helpers/TranslatorTest.php create mode 100644 tests/Unit/Middleware/SetLocaleTest.php diff --git a/config/app.php b/config/app.php index 13d4d22b..ea394b8e 100644 --- a/config/app.php +++ b/config/app.php @@ -14,6 +14,7 @@ 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\Middleware\RouteDispatcherServiceProvider::class, @@ -24,6 +25,7 @@ return [ 'middleware' => [ \Engelsystem\Middleware\SendResponseHandler::class, \Engelsystem\Middleware\ExceptionHandler::class, + \Engelsystem\Middleware\SetLocale::class, \Engelsystem\Middleware\RouteDispatcher::class, \Engelsystem\Middleware\RequestHandler::class, ], diff --git a/includes/engelsystem.php b/includes/engelsystem.php index eb5b220a..4c096b43 100644 --- a/includes/engelsystem.php +++ b/includes/engelsystem.php @@ -21,12 +21,6 @@ if ($app->get('config')->get('maintenance')) { } -/** - * Init translations - */ -gettext_init(); - - /** * Init authorization */ 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 @@ -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/sys_form.php b/includes/sys_form.php index 05df4c15..9cb6f38d 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, '
' @@ -74,7 +77,7 @@ function form_date($name, $label, $value, $start_date = '', $end_date = '')