concretecms
concretecms copied to clipboard
Draft: Twig support
This PR attempts to add support for Twig templates while maintaining backward compatibility by modifying \Concrete\Core\Filesystem\FileLocator to optionally check for additional file extensions. Doing it this way, we can override a concrete/blocks/foo/view.php with a application/blocks/foo/view.html.twig and vice-versa.
Why?
Twig templates provide some strong benefits over our existing PHP templates:
- Output is sanitized by default. Rather than having to opt-in to safety with
h($something), Twig has us opt-out with{{ something | raw }}. This makes it a lot easier to review code changes for XSS because you'll pretty much never opt-out unless you're specifically outputting known HTML - Template files are distinguishable from class / php files. Today the PHP tooling ecosystem requires us to use different tools (or at least different configuration) for template files than we do for class / pure PHP files due to the mixed HTML being confusing. This leads to well-tested projects needing to manually list out the names of files that get one configuration vs files that get another. By using twig templates, we eliminate mixed php and html from the codebase and the
.html.twigextension makes it obvious which files are intended to be templates and which files are not #11853 - Template designers know twig Using a more familiar paradigm like Twig allows symfony / drupal / python designers to work on templates for Concrete without needing to know or understand PHP. Front end developers / designers can build and iterate template files while back end / php developers focus on the back-end functionality.
Why Twig?
| Feature | Twig | Blade | Latte | Plates |
|---|---|---|---|---|
| Standalone | ✅ | ❌ | ✅ | ✅ |
| Extensible | ✅ | ✅ | ✅ | ✅ |
| PHP 7.3 | ✅ | ❌ | ❌ | ✅ |
| Secure by default | ✅ | ✅ | ✅ | ❌ |
| No dependencies | ✅ | ❌ | ✅ | ✅ |
| Well-established | ✅ | ✅1 | ✅2 | ❌ |
- While Blade is well-established, its syntax isn't used anywhere other than within the Laravel ecosystem
- Latte has been around for a long time, but it also has unicorn syntax and isn't used much outside of nette
Overview
This PR adds template support with two main changes:
- Template support in
FileLocator- Relevant
FileLocatormethods, and methods that exist solely to proxy to aforementioneFileLocatormethods have been updated to support a new$template = falseflag. Passingtruehere tells theFileLocatorthat we're loading a template and not something likedb.xmlorcontroller.phporpage_theme.phpwhich allows theFileLocatorto search for additional file extensions other than just.php.
- Relevant
\Concrete\Core\Filesystem\TemplateServicenow is in charge of rendering templates.- Anywhere we're currently doing
extract($scopeItems); include $file;to render templates, we want to start usingTemplateService->renderTemplate($file, $scopeItems). ->renderTemplatewill automatically render Twig templates using Twig and.phptemplates in the current backwards compatible way.->renderTemplatealso includes a$bindTo = nullargument that allows binding the$thisvariable properly when including templates for backwards compatibility. For example:$service->renderTemplate($file, $scopeItems, $this);
- Anywhere we're currently doing
Additionally, this PR includes some tooling to support configuring the Twig environment used.
app.twigconfig keyapp.twig.extensionsarray<string, \Twig\Extension\ExtensionInterface|class-string<\Twig\Extension\ExtensionInterface>>is an array of \Twig\Extension\ExtensionInterface class strings or instancesapp.twig.debugbool
\Core\Filesystem\TwigFactoryhandles creating Twig environments and all the wiring required.
Using Twig templates
In order to use a twig template, name your template file your_template.html.twig instead of your_template.php. Concrete's override detection should automatically identify the .html.twig file the same way it would the .php file and render it with Twig.
An example Twig default.html.twig for a theme might look like this:
{# Extend our skeleton #}
{% extends 'templates/skeleton.html.twig' %}
{% block content %}
<main class='main-content'>
{{ Area('Main') | raw }}
</main>
<footer>
{% for i in 1..4 %}
<div class='col-3'>
{{ Area('Footer ' ~ i) }}
</div>
{% endfor %}
{% endblock %}
And an example block custom template wrapped_content.html.twig for the content block might look like this:
<div class="border-top border-primary border-5 pt-5 ccm-block-content">
{% if not content and c.isEditMode() %}
<div class="ccm-block-content">{{ t('Empty Content Block.') }}</div>
{% else %}
{{ content | raw }}
{% endif %}
</div>
Extending Twig
Global extensions
If you'd like to add your extension to every Twig instance you can either add it to your app.php config file, or extend it using the Concrete App instance
With config
// /application/config/app.php
return [
'twig' => [
'extensions' => [
'my_extension' => \My\Namespace\MyExtension::class
]
]
];
With Concrete App Instance
// In /application/bootstrap/app.php, or an on_start, or a service provider
use Concrete\Core\Filesystem\TwigFactory;
$app->extend(TwigFactory::class, function (TwigFactory $factory): TwigFactory {
$factory->addExtension(new \My\Namespace\MyExtension());
// You can also add global variables this way
$factory->addGlobal('someGlobal', $someGlobal);
return $factory
})
TODO
Support twig anywhere we support php templates today (Or enough for a v1)
- [x] Block view support
- [x] Block template support
- [x] Theme view support
- [x] Element support
- [x] Single page support
- [x] Container view support
- [x] Board views
- [ ] Board slots rendering properly
- [ ] Auth type views
- [ ] Permission views
- [ ] Attribute views
Core twig extension needs minimal functionality
- [x]
t()Internationalization support is a requirement - [x]
activeLocale()andactiveLanguage()Mapping toLocalizationstatic methods of the same name - [x]
h()(Even though it's not typically needed, we might need to dot('foo <b>%s</b>', h(someVar))) - [x]
app()This is a bit of a slippery slope, but I think locking down twig templates at this stage would make adoption challenging - [x]
include()/require()We want to still be able to include PHP in Twig at least in the beginning. There are examples in the atomik theme where block templates rely on the ability to load in PHP files from the core. - [x]
config()This is already available withapp('config')but I like having short functions for the things people might do often - [x]
url()Maps toResolverManager->resolve() - [x]
Area()GlobalArea()Stack()ContainerArea()These currently map tonew FluentArea(new $type). FluentArea enables chaining calls and simplifies->display() - [x]
element()For including elements - [x]
getCurrentPage() - [x]
pageByID() - [x]
fileByID() - [x]
authTypeGetList() - [x]
authTypeByHandle() - [x]
authTypeByID() - [x]
foo | bufferMethod('methodName', [arg1, arg2], thisValue)forob_start()output buffering of things that otherwise would output prematurely - [x]
foo | preg_replace(regex, replace)for better replacement
Planning
- [ ] Identify what needs to happen for translations to work happily https://github.com/concrete5-community/translation-library/issues/21
- [ ] Identify all remaining views that need support
- [ ] Identify necessary v1 extension functionality. Look at latte and blade for inspiration for necessary functions/filters/tags
- [ ] Identify what else is left to do