Added Twig template renderer, closes #338
This commit is contained in:
parent
a1bc763a16
commit
bb3d16d273
|
@ -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": {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,5 +13,5 @@ function credits_title()
|
|||
*/
|
||||
function guest_credits()
|
||||
{
|
||||
return view(__DIR__ . '/../../templates/guest_credits.html');
|
||||
return view(__DIR__ . '/../../templates/pages/credits.html');
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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']);
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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">
|
|
@ -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'));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue