Added Twig template functions

This commit is contained in:
Igor Scheller 2018-08-26 12:23:47 +02:00
parent bb3d16d273
commit df6360044b
19 changed files with 487 additions and 24 deletions

View File

@ -12,6 +12,7 @@ class ConfigServiceProvider extends ServiceProvider
$configFile = config_path('config.php'); $configFile = config_path('config.php');
$config = $this->app->make(Config::class); $config = $this->app->make(Config::class);
$this->app->instance(Config::class, $config);
$this->app->instance('config', $config); $this->app->instance('config', $config);
$config->set(require $defaultConfigFile); $config->set(require $defaultConfigFile);

View File

@ -17,6 +17,7 @@ class SessionServiceProvider extends ServiceProvider
$this->app->bind(SessionStorageInterface::class, 'session.storage'); $this->app->bind(SessionStorageInterface::class, 'session.storage');
$session = $this->app->make(Session::class); $session = $this->app->make(Session::class);
$this->app->instance(Session::class, $session);
$this->app->instance('session', $session); $this->app->instance('session', $session);
/** @var Request $request */ /** @var Request $request */

View File

@ -9,6 +9,7 @@ class UrlGeneratorServiceProvider extends ServiceProvider
public function register() public function register()
{ {
$urlGenerator = $this->app->make(UrlGenerator::class); $urlGenerator = $this->app->make(UrlGenerator::class);
$this->app->instance(UrlGenerator::class, $urlGenerator);
$this->app->instance('http.urlGenerator', $urlGenerator); $this->app->instance('http.urlGenerator', $urlGenerator);
} }
} }

View File

@ -284,21 +284,11 @@ class LegacyMiddleware implements MiddlewareInterface
} }
return response(view('layouts/app', [ return response(view('layouts/app', [
'theme' => isset($user) ? $user['color'] : config('theme'),
'title' => $title, 'title' => $title,
'atom_link' => ($page == 'news' || $page == 'user_meetings') 'atom_feed' => ($page == 'news' || $page == 'user_meetings') ? $parameters : [],
? ' <link href="'
. page_link_to('atom', $parameters)
. '" type = "application/atom+xml" rel = "alternate" title = "Atom Feed">'
: '',
'start_page_url' => page_link_to('/'),
'credits_url' => page_link_to('credits'),
'menu' => make_menu(), 'menu' => make_menu(),
'content' => msg() . $content, 'content' => msg() . $content,
'header_toolbar' => header_toolbar(), 'header_toolbar' => header_toolbar(),
'faq_url' => config('faq_url'),
'contact_email' => config('contact_email'),
'locale' => locale(),
'event_info' => EventConfig_info($event_config) . ' <br />' 'event_info' => EventConfig_info($event_config) . ' <br />'
]), $status); ]), $status);
} }

View File

@ -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']),
];
}
}

View File

@ -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 : [],
];
}
}

View File

@ -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']),
];
}
}

View File

@ -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);
}
}

View File

@ -3,14 +3,40 @@
namespace Engelsystem\Renderer; namespace Engelsystem\Renderer;
use Engelsystem\Container\ServiceProvider; 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_Environment as Twig;
use Twig_LoaderInterface as TwigLoaderInterface; use Twig_LoaderInterface as TwigLoaderInterface;
class TwigServiceProvider extends ServiceProvider class TwigServiceProvider extends ServiceProvider
{ {
/** @var array */
protected $extensions = [
'config' => Config::class,
'globals' => Globals::class,
'session' => Session::class,
'url' => Url::class,
];
public function register() public function register()
{ {
$this->registerTwigEngine(); $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() protected function registerTwigEngine()
@ -20,12 +46,30 @@ class TwigServiceProvider extends ServiceProvider
$twigLoader = $this->app->make(TwigLoader::class, ['paths' => $viewsPath]); $twigLoader = $this->app->make(TwigLoader::class, ['paths' => $viewsPath]);
$this->app->instance(TwigLoader::class, $twigLoader); $this->app->instance(TwigLoader::class, $twigLoader);
$this->app->instance(TwigLoaderInterface::class, $twigLoader); $this->app->instance(TwigLoaderInterface::class, $twigLoader);
$this->app->instance('twig.loader', $twigLoader);
$twig = $this->app->make(Twig::class); $twig = $this->app->make(Twig::class);
$this->app->instance(Twig::class, $twig); $this->app->instance(Twig::class, $twig);
$this->app->instance('twig.environment', $twig);
$twigEngine = $this->app->make(TwigEngine::class); $twigEngine = $this->app->make(TwigEngine::class);
$this->app->instance('renderer.twigEngine', $twigEngine); $this->app->instance('renderer.twigEngine', $twigEngine);
$this->app->tag('renderer.twigEngine', ['renderer.engine']); $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']);
}
} }

View File

@ -1,3 +1,4 @@
{% set theme = user.color|default(config('theme')) %}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -10,7 +11,9 @@
<link rel="stylesheet" type="text/css" href="vendor/bootstrap-datepicker-1.7.1/css/bootstrap-datepicker3.min.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-2.1.1.min.js"></script>
<script type="text/javascript" src="vendor/jquery-ui.min.js"></script> <script type="text/javascript" src="vendor/jquery-ui.min.js"></script>
{{ atom_link|raw }} {% if atom_feed -%}
<link href="{{ url('atom', atom_feed) }}" type="application/atom+xml" rel="alternate" title="Atom Feed">
{% endif %}
{% endblock %} {% endblock %}
</head> </head>
<body> <body>
@ -27,14 +30,16 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="{{ start_page_url }}"> <a class="navbar-brand" href="{{ url('/') }}">
<span class="icon-icon_angel"></span> <strong class="visible-lg-inline">ENGELSYSTEM</strong> <span class="icon-icon_angel"></span> <strong class="visible-lg-inline">ENGELSYSTEM</strong>
</a> </a>
</div> </div>
{% block navbar %} {% block navbar %}
<div class="collapse navbar-collapse" <div class="collapse navbar-collapse" id="navbar-collapse-1">
id="navbar-collapse-1">{{ menu|raw }} {{ header_toolbar|raw }}</div> {{ menu|raw }}
{{ header_toolbar|raw }}
</div>
{% endblock %} {% endblock %}
</div> </div>
{% endblock %} {% endblock %}
@ -50,11 +55,13 @@
{% block eventinfo %} {% block eventinfo %}
{{ event_info|raw }} {{ event_info|raw }}
{% endblock %} {% endblock %}
<a href="{{ faq_url }}">FAQ</a> <a href="{{ config('faq_url') }}">FAQ</a>
· <a href="{{ contact_email }}"><span class="glyphicon glyphicon-envelope"></span> Contact</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/issues">Bugs / Features</a>
· <a href="https://github.com/engelsystem/engelsystem/">Development Platform</a> · <a href="https://github.com/engelsystem/engelsystem/">Development Platform</a>
· <a href="{{ credits_url }}">Credits</a> · <a href="{{ url('credits') }}">Credits</a>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
@ -69,7 +76,7 @@
<script type="text/javascript" src="vendor/moment-with-locales.min.js"></script> <script type="text/javascript" src="vendor/moment-with-locales.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function () {
moment.locale("{{ locale|escape('js') }}"); moment.locale("{{ session_get('locale')|escape('js') }}");
}); });
</script> </script>
<script type="text/javascript" src="js/moment-countdown.js"></script> <script type="text/javascript" src="js/moment-countdown.js"></script>

View File

@ -23,8 +23,13 @@ class ConfigServiceProviderTest extends ServiceProviderTest
Application::setInstance($app); Application::setInstance($app);
$this->setExpects($app, 'make', [Config::class], $config); $this->setExpects($app, 'make', [Config::class], $config);
$this->setExpects($app, 'instance', ['config', $config]);
$this->setExpects($app, 'get', ['path.config'], __DIR__ . '/../../../config', $this->atLeastOnce()); $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, 'set', null, null, $this->exactly(2));
$this->setExpects($config, 'get', [null], []); $this->setExpects($config, 'get', [null], []);

View File

@ -54,6 +54,7 @@ class SessionServiceProviderTest extends ServiceProviderTest
->method('instance') ->method('instance')
->withConsecutive( ->withConsecutive(
['session.storage', $sessionStorage], ['session.storage', $sessionStorage],
[Session::class, $session],
['session', $session] ['session', $session]
); );
@ -88,10 +89,11 @@ class SessionServiceProviderTest extends ServiceProviderTest
$sessionStorage, $sessionStorage,
$session $session
); );
$app->expects($this->exactly(2)) $app->expects($this->exactly(3))
->method('instance') ->method('instance')
->withConsecutive( ->withConsecutive(
['session.storage', $sessionStorage], ['session.storage', $sessionStorage],
[Session::class, $session],
['session', $session] ['session', $session]
); );

View File

@ -21,7 +21,12 @@ class UrlGeneratorServiceProviderTest extends ServiceProviderTest
$app = $this->getApp(); $app = $this->getApp();
$this->setExpects($app, 'make', [UrlGenerator::class], $urlGenerator); $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 = new UrlGeneratorServiceProvider($app);
$serviceProvider->register(); $serviceProvider->register();

View File

@ -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);
}
}

View File

@ -0,0 +1,48 @@
<?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 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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -7,16 +7,89 @@ use Engelsystem\Renderer\TwigLoader;
use Engelsystem\Renderer\TwigServiceProvider; use Engelsystem\Renderer\TwigServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest; use Engelsystem\Test\Unit\ServiceProviderTest;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use ReflectionClass as Reflection;
use stdClass;
use Twig_Environment as Twig; use Twig_Environment as Twig;
use Twig_ExtensionInterface as ExtensionInterface;
use Twig_LoaderInterface as TwigLoaderInterface; use Twig_LoaderInterface as TwigLoaderInterface;
class TwigServiceProviderTest extends ServiceProviderTest class TwigServiceProviderTest extends ServiceProviderTest
{ {
/** /**
* @covers \Engelsystem\Renderer\TwigServiceProvider::register * @covers \Engelsystem\Renderer\TwigServiceProvider::register
* @covers \Engelsystem\Renderer\TwigServiceProvider::registerTwigEngine * @covers \Engelsystem\Renderer\TwigServiceProvider::registerTwigExtensions
*/ */
public function testRegister() 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 */ /** @var TwigEngine|MockObject $htmlEngine */
$twigEngine = $this->createMock(TwigEngine::class); $twigEngine = $this->createMock(TwigEngine::class);
@ -41,12 +114,14 @@ class TwigServiceProviderTest extends ServiceProviderTest
$twigEngine $twigEngine
); );
$app->expects($this->exactly(4)) $app->expects($this->exactly(6))
->method('instance') ->method('instance')
->withConsecutive( ->withConsecutive(
[TwigLoader::class, $twigLoader], [TwigLoader::class, $twigLoader],
[TwigLoaderInterface::class, $twigLoader], [TwigLoaderInterface::class, $twigLoader],
['twig.loader', $twigLoader],
[Twig::class, $twig], [Twig::class, $twig],
['twig.environment', $twig],
['renderer.twigEngine', $twigEngine] ['renderer.twigEngine', $twigEngine]
); );
@ -58,6 +133,23 @@ class TwigServiceProviderTest extends ServiceProviderTest
$this->setExpects($app, 'tag', ['renderer.twigEngine', ['renderer.engine']]); $this->setExpects($app, 'tag', ['renderer.twigEngine', ['renderer.engine']]);
$serviceProvider = new TwigServiceProvider($app); $serviceProvider = new TwigServiceProvider($app);
$this->setExtensionsTo($serviceProvider, []);
$serviceProvider->register(); $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);
}
} }