☕ Latte: the intuitive and fast template engine for those who want the most secure PHP sites.

Overview

Latte: amazing template engine for PHP

Downloads this Month Tests Coverage Status Latest Stable Version License

Introduction

Latte is a template engine for PHP which eases your work and ensures the output is protected against vulnerabilities, such as XSS.

  • Latte is fast: it compiles templates to plain optimized PHP code.
  • Latte is secure: it is the first PHP engine introducing content-aware escaping.
  • Latte speaks your language: it has intuitive syntax and helps you to build better websites easily.

Documentation can be found on the website.

Support Latte

Do you like Latte? Are you looking forward to the new features?

Buy me a coffee

Thank you!

Getting Started

Although PHP is originally a templating language, it is not particularly suited for writing templates. Let's have a look at an example of a PHP template that prints an array $items as a list:

<?php if ($items): ?>
	<?php $counter = 1 ?>
	<ul>
	<?php foreach ($items as $item): ?>
		<li id="item-<?php echo $counter++ ?>"><?php
		echo htmlSpecialChars(mb_convert_case($item, MB_CASE_TITLE)) ?>
		</li>
	<?php endforeach ?>
	</ul>
<?php endif?>

The code is rather confusing. Moreover, we must not forget to call htmlSpecialChars function. That's why there are so many different template engines for PHP. One of the best template engines is part of Nette Framework and it is called Latte. You'll love it!

The same template as the one above can be written easily in Latte:

<ul n:if="$items">
{foreach $items as $item}
	<li id="item-{$iterator->counter}">{$item|capitalize}</li>
{/foreach}
</ul>

As you can see there are two types of macros:

  • macro in braces, for example {foreach …}
  • n:macro, for example n:if="…"

How to render template? Just install Latte (see below) and run this code:

$latte = new Latte\Engine;
$latte->setTempDirectory('/path/to/tempdir');
$parameters['items'] = array('one', 'two', 'three');
$latte->render('template.latte', $parameters);

Installation

The recommended way to install Latte is via Composer (alternatively you can download package):

composer require latte/latte

Latte requires PHP version 7.1 and supports PHP up to 8.0.

Usage

You can find detailed description of all the default macros on the extra page. Furthermore, you can make your own macros.

Each pair macro, such as {if} … {/if}, operating upon single HTML element can be written in n:macro notation. So, it is possible to write the {foreach} macro in the same manner:

<ul n:if="$items">
	<li n:foreach="$items as $item">{$item|capitalize}</li>
</ul>

With n:macros you can do much more interesting tricks as you will see in a moment.

{$item|capitalize} macro which prints the $item variable contains so called filter, in this case the capitalize filter which makes the first letter of each word uppercase.

Very important feature of Latte is that it escapes variables by default. Escaping is needed when printing a variable because we have to convert all the characters which have a special meaning in HTML to other sequences. In case we forget it can lead to a serious security hole called Cross Site Scripting (XSS).

Because of different escaping functions that are needed in different documents and different parts of a page, Latte features a unique technology of Context-Aware Escaping which recognizes the context in which the macro is placed and chooses the right escaping mode. You don't have to worry that your coder forgets about it causing you goose bumps because of a security hole. Which is great!

If the $item variable stores an HTML code and you want to print it without any alteration you just add the modifier noescape: {$item|noescape}. Forgetting the modifier mark won't cause any security holes in spirit of „less code, more security“ principle.

You can still use PHP inside the macros normally, including comments as well. But Latte also extends the PHP syntax with three pleasant features:

  1. array can be written as [1, 2, 3], which is the same as array(1, 2, 3) in PHP
  2. we can omit quotes around the strings consisting of letters, numbers and dashes
  3. short condition notation $a ? 'b' which is the same as $a ? 'b' : null in PHP

For example:

{foreach [a, b, c] as $id} ... {/foreach}

{$cond ? hello}  // prints 'hello' if $cond equals true

Latte also has a {* comment macro *} which doesn't get printed to the output.

n:macros

We showed that n:macros are supposed to be written directly into HTML tags as their special attributes. We also said that every pair macro (e.g. {if} … {/if}) can be written in n:macro notation. The macro then corresponds to the HTML element in which it is written:

{var $items = ['I', '♥', 'Nette Framework']}

<p n:foreach="$items as $item">{$item}</p>

Prints:

<p>I</p>
<p></p>
<p>Nette Framework</p>

By using inner- prefix we can alter the behavior so that the macro applies only to the body of the element:

<div n:inner-foreach="$items as $item">
	<p>{$item}</p>
	<hr>
</div>

Prints:

<div>
	<p>I</p>
	<hr>
	<p></p>
	<hr>
	<p>Nette Framework</p>
	<hr>
</div>

Or by using tag- prefix the macro is applied on the HTML tags only:

<p><a href="{$url}" n:tag-if="$url">Title</a></p>

Depending on the value of $url variable this will print:

// when $url is empty
<p>Title</p>

// when $url equals 'https://nette.org'
<p><a href="https://nette.org">Title</a></p>

However, n:macros are not only a shortcut for pair macros, there are some pure n:macros as well, for example the coder's best friend n:class macro.

Filters

Latte allows calling filters by using the pipe sign notation (preceding space is allowed):

<h1>{$heading|upper}</h1>

Filters (or modifiers) can be chained, in that case they apply in order from left to right:

<h1>{$heading|lower|capitalize}</h1>

Parameters are put after the filter name separated by colon or comma:

<h1>{$heading|truncate:20,''}</h1>

See the summary of standard filters and how to make user-defined filters.

In templates we can use functions which change or format the data to a form we want. They are called filters. See the summary of the default filters.

Filter can be registered by any callback or lambda function:

$latte = new Latte\Engine;
$latte->addFilter('shortify', function ($s) {
	return mb_substr($s, 0, 10); // shortens the text to 10 characters
});

In this case it would be better for the filter to get an extra parameter:

$latte->addFilter('shortify', function ($s, $len = 10) {
	return mb_substr($s, 0, $len);
});

We call it in a template like this:

<p><?php echo $template->shortify($text, 100); ?></p>

Latte simplifies the notation - filters are denoted by the pipe sign, they can be chained (they apply in order from left to right). Parameters are separated by colon or comma:

<p>{$text|shortify:100}</p>

Performance

Latte is fast. It compiles the templates to native PHP code and stores them in cache on the disk. So they are as fast as if they would have been written in pure PHP.

The template is automatically recompiled each time we change the source file. While developing you just need to edit the templates in Latte and changes are visible in your browser instantly.

Debugging

With each error or typo you will be informed by the Debugger with all the luxury. The template source code is displayed and the red line marks the error showing error message as well. With just a single click you can open the template in your favorite editor and fix the error at once. Easy peasy!

If you are using an IDE with code stepping you can go through the generated PHP code of the template.

Usability

Latte syntax wasn't invented by engineers but came up from webdesigner's practical requests. We were looking for the friendliest syntax with which you can write even the most problematic constructions comfortably enough. You will be surprised how much help Latte can be.

You can find macros for advanced layout managing, for template inheritance, nested blocks and so on. Syntax comes from PHP itself so you don't have to learn anything new and you can leverage your know-how.

Context-Aware Escaping

Although the Cross Site Scripting (XSS) is one of the trivial ways of exploiting a web page it is the most common vulnerability but very serious. It can lead to identity theft and so on. The best defense is consistent escaping of printed data, ie. converting the characters which have a special meaning in the given context.

If the coder omits the escaping a security hole is made. That's why template engines implement automated escaping. The problem is that the web page has different contexts and each has different rules for escaping printed data. A security hole then shows up if the wrong escaping functions are used.

But Latte is sophisticated. It features unique technology of Context-Aware Escaping which recognizes the context in which the macro is placed and chooses the right escaping mode. What does that mean?

Latte doesn't need any manual work. All is done automatically, consistently and correctly. You don't have to worry about security holes.

Lets see how it works:

<p onclick="alert({$movie})">{$movie}</p>

<script>var movie = {$movie};</script>

If $movie variable stores 'Amarcord & 8 1/2' string it generates the following output. Notice different escaping used in HTML and JavaScript and also in onclick attribute:

<p onclick="alert(&quot;Amarcord &amp; 8 1\/2&quot;)">Amarcord &amp; 8 1/2</p>

<script>var movie = "Amarcord & 8 1\/2";</script>

Thanks to Context-Aware Escaping the template is simple and your application perfectly secured against Cross Site Scripting. You can use PHP variables natively inside the JavaScript!

JavaScript

Strings in JavaScript are escaped including quotes. If you want to put variable into another string, simply concatenate them:

<script>
	alert('Hello ' + {$name} + '!');  # good
	alert('Hello {$name} !');  # bad
</script>

A pretty output

Sticklers will enjoy the look of the HTML output which Latte generates. All tags are indented as they are supposed to. The code looks like it has been processed with some kind of HTML code beautifier :-)

Comments
  • Engine: optimized loadCacheFile() performance

    Engine: optimized loadCacheFile() performance

    It's mostly similar to what DI\ContainerLoader does. Shouldn't we write the code to temporary file first and them use rename?


    Edit: I've tried slightly different approach. Will it work?

    opened by JanTvrdik 28
  • Case-sensitive filters

    Case-sensitive filters

    Hi. I tryed to prepare pull request with case-sensitive filters. I hope it is ok.

    Motivation:

    1. Almost every part of Nette is case sensitive now, so filters should be sensitive too.
    2. Its cleaner when exists the only one way to call filter (now you can call {$var|leftPad} and also {$var|leftpad}, {$var|Leftpad} etc.).
    3. Now, when you register user static filter (for example myFilter) and call it in Latte with proper camel case (exactly as you register it, for example {$var|myFilter}), in every single filter call magic method FilterExecutor::__get() is called, although when calling lowercase its is called directly via property (except the first call). From my point of view this is not correct that calling filter in lowercase ("bad cases") is quicker than "proper case" call.
    4. Code is now a little bit nicer.
    5. It opens doors to next step (and simplify its implementation) - full support of dynamic macros in method FilterExecutor::renderContent() I want to implement.

    Notes:

    • Back compatibility of builtin Nette filters is implemented via property $_deprecated with deprecation notice.
    • If its not ok to deprecate lowercase filters, i can move lowercase filters back to array _static so we will support it too.
    • When you use user-defined macro, it throws Exception with correct notice, that you are calling filter with "bad cases".
    • What with substr filter? I think good camel case name should be subString (see same name method in class Filters), but someone maybe want to use it with same name as PHP function substr. What do you think?
    • Yes, it is BC break, but I think it is worth it. Same as case sensitivity in Routing, which is allready implemented.

    If you have any suggestions, don't hesitate to comment :-)

    opened by EdaCZ 17
  • Macros: allowed to specify implicit behavior as

    Macros: allowed to specify implicit behavior as "inner" or "tag"

    example:

    $macroSet->addMacro('alwaysIn', '?>{<?php', '?>}<?php', NULL, MacroNode::PREFIX_INNER);
    

    will transform

    <div n:alwaysIn>foo</div>
    

    into

    <div>{foo}</div>
    
    opened by richard-ejem 16
  • [WIP] snippets refactoring

    [WIP] snippets refactoring

    Snippets are now completely independent on the nette/application. I will send related PR to the nette/application in few minutes/hours.

    What do you think?

    opened by matej21 15
  • There is a way to bypass allowFunctions

    There is a way to bypass allowFunctions

    Version: 2.10.5

    Bug Description

    There is a way to bypass allowFunctions that will affect security.

    Steps To Reproduce

    <?php
    error_reporting(0);
    require 'vendor/autoload.php';
    $latte = new Latte\Engine;
    $policy = new Latte\Sandbox\SecurityPolicy;
    $policy->allowFilters($policy::ALL);
    $policy->allowMacros(['if','=']);
    $policy->allowFunctions(['strlen']);
    $latte->setPolicy($policy);
    $latte->setSandboxMode();
    $latte->setAutoRefresh(false);
    file_put_contents('index.latte',"{=system\x00('whoami')}");
    $latte->render('index.latte');
    

    This will execute the system function.

    Expected Behavior

    Should throw an error not allowed by system function

    Possible Solution

    Use rigorous regular expression segmentation, or add more rigorous judgments in isFunctionAllowed function

    opened by JinYiTong 14
  • Throwing an exception in acquireLock(...) fails

    Throwing an exception in acquireLock(...) fails

    Version: 2.10 PHP: 8.0.3

    Bug Description

    Throwing an exception at Engine.php:247 fails as Latte expects the function error_get_last() to always return an array from what it tries to read the message key. However, the aforementioned function can also return NULL, see https://www.php.net/manual/en/function.error-get-last.php

    throw new RuntimeException("Unable to create directory '$dir'. " . error_get_last()['message']);
    

    Steps To Reproduce

    set_error_handler('myErrorHandler');// <- this is what makes the error_get_last() func to always return NULL instead of an array
    $latte = new Latte\Engine();
    $latte->setTempDirectory('temp');// <- make sure your app can't access or make that dir!!!
    $output = $latte->renderToString('random_template.latte');
    
    opened by sukovf 13
  • Latte 2.3 and 2.2 not compatible with PHP 7.2

    Latte 2.3 and 2.2 not compatible with PHP 7.2

    Version: 2.3.* and 2.2.* (maybe older versions too, 2.4.* works fine)

    Bug Description

    composer.json allows PHP 7.2, but with this version of PHP, Object is not allowed as class name : Fatal error: Cannot use 'Object' as class name as it is reserved in /var/www/phpbenchmarks/benchmark/latte/vendor/latte/latte/src/Latte/Object.php on line 16

    Steps To Reproduce

    Hello World snippet to reproduce it :

    $latte = (new Latte\Engine)
        ->setTempDirectory($cacheDir)
        ->setAutoRefresh(true);
    
    $latte->render(__DIR__ . '/templates/hello-world.latte');
    

    Expected Behavior

    Don't allow PHP >= 7.2

    Possible Solution

    Change class name, but i think it could not be changed without BC ?

    opened by phpbenchmarks 13
  • Long PHP code enclosed in {php ... } macro causes Latte\RegexpException.

    Long PHP code enclosed in {php ... } macro causes Latte\RegexpException.

    Description

    After upgrading from Nette 2.3.x to Nette 2.4.x I replaced all occurences of <?php ... ?> to {php ... }. I think PHP code is not parsed properly by regexp in Parser.php file if I use long code.

    Steps To Reproduce

    This is a sample of minimal latte template:

    {extends none}
    
    {php
    $data = [
        [
            'name' => 'Orders - create',
            'url' => 'orders/create',
            'parameters' => [
                [
                    'name' => 'email',
                    'type' => 'string',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'E-mail zákazníka.',
                ],
                [
                    'name' => 'phone',
                    'type' => 'string',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'Telefón zákazníka.',
                ],
                [
                    'name' => 'shipping_id',
                    'type' => 'int',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'ID dopravy.',
                ],
                [
                    'name' => 'payment_id',
                    'type' => 'int',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'ID platby.',
                ],
                [
                    'name' => 'invoice_name',
                    'type' => 'string',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'Fakturačná adresa: meno',
                ],
                [
                    'name' => 'invoice_street',
                    'type' => 'string',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'Fakturačná adresa: ulica',
                ],
                [
                    'name' => 'invoice_postcode',
                    'type' => 'string',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'Fakturačná adresa: PSČ',
                ],
                [
                    'name' => 'invoice_city',
                    'type' => 'string',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'Fakturačná adresa: mesto',
                ],
                [
                    'name' => 'invoice_country_id',
                    'type' => 'int',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'Fakturačná adresa: ID krajiny',
                ],
                [
                    'name' => 'delivery_name',
                    'type' => 'string',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'Doručovacia adresa: meno',
                ],
                [
                    'name' => 'delivery_street',
                    'type' => 'string',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'Doručovacia adresa: ulica',
                ],
                [
                    'name' => 'delivery_postcode',
                    'type' => 'string',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'Doručovacia adresa: PSČ',
                ],
                [
                    'name' => 'delivery_city',
                    'type' => 'string',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'Doručovacia adresa: mesto',
                ],
                [
                    'name' => 'delivery_country_id',
                    'type' => 'int',
                    'required' => 'yes',
                    'default' => '',
                    'description' => 'Doručovacia adresa: ID krajiny',
                ],
                [
                    'name' => 'company_name',
                    'type' => 'string',
                    'required' => 'no',
                    'default' => '',
                    'description' => 'Názov spoločnosti.',
                ],
                [
                    'name' => 'company_uid',
                    'type' => 'string',
                    'required' => 'yes/no',
                    'default' => '',
                    'description' => 'IČO (povinné v prípade vyplnenia parametru company_name)',
                ],
                [
                    'name' => 'company_vatid',
                    'type' => 'string',
                    'required' => 'yes/no',
                    'default' => '',
                    'description' => 'DIČ / IČ DPH (povinné v prípade vyplnenia parametru company_name)',
                ],
                [
                    'name' => 'company_vatpayer',
                    'type' => 'bool',
                    'required' => 'yes/no',
                    'default' => '',
                    'description' => 'Platca DPH (povinné v prípade vyplnenia parametru company_name)',
                ],
            ],
        ],
    ];
    }
    

    It throws Latte\CompileException with message Thrown exception 'Unknown error' in test.latte:138

    I think this unknown error may be related to the new PHP constant PREG_JIT_STACKLIMIT_ERROR introduced in PHP 7.0.0.

    Latte\Parser::parseMacroTag() method is the place where the preg_last_error() returns number 6 (= PREG_JIT_STACKLIMIT_ERROR).

    Software versions

    • PHP 7.1.0
    • Apache/2.4.25 (Win64) OpenSSL/1.0.2j PHP/7.1.0
    • Tracy 2.4.4
    • Nette Framework 2.4
    opened by pavolbiely 13
  • Revert Filters: Update nl2br to return Latte\Runtime\Html object

    Revert Filters: Update nl2br to return Latte\Runtime\Html object

    Former change could cause potential hidden XSS errors as the whole string was being unescaped.

    This reverts commit 86b55f5d8e7b0ed182404df987107e905a7f974f.

    opened by finwe 13
  • Compile literals

    Compile literals

    • Would it be possible to evaluate all the literal calls like translation of constants, or including another file into the template, etc?

    • The reason for this, is to further improve performance. This could be a chance to make it actually faster than pure php, with the help of compilation

    opened by pixobit 12
  • Propagate variable types to generated code to allow statical analysis

    Propagate variable types to generated code to allow statical analysis

    • new feature
    • BC break? no

    With this change Latte will propagate known types infomation from {varType}, {define} and {parameters} into compiled PHP code in form of /** @var type $var */ annotations. This will allow statical anylysis of resulting code by static analysers like PHPStan, Psalm, etc.

    See discussion about this feature here https://github.com/nette/latte/issues/262

    (Previous PR targeted onto master here #275)

    opened by MartinMystikJonas 12
  • varPrint and templatePrint are not working with renderToString

    varPrint and templatePrint are not working with renderToString

    Version: 3.0

    Bug Description

    Adding {varPrint} or {templatePrint} to a template that is rendered with renderToString creates an empty output.

    Expected Behavior

    Actual output of {varPrint} or {templatePrint} is sent.

    Possible Solution

    The cause for this bug is the start of an output buffer in Template::capture that is than discarded due to the printers calling exit.

    Instead of calling exit a custom exception could be thrown, that is caught in Template::capture to output the current buffer and exit then. It could also be caught in Engine::render and to just call exit there.

    opened by Khartir 0
  • Cannot call unset() in a {php} block

    Cannot call unset() in a {php} block

    Version: 3.0.4

    Bug Description

    After an upgrade to Latte 3, it is no longer possible to call the unset() function in a {php} Latte block. I am aware of the Raw PHP Extension however I think that this function call should be allowed in a basic version of the {php} block.

    I am however not sure if this is a design decisions or a bug.

    Steps To Reproduce

    $latte = new \Latte\Engine();
    $latte->setLoader(new \Latte\Loaders\StringLoader(['template' => '{$foo}{php unset($foo)}']));
    $latte->render('template', ['foo' => 'hello']);
    

    Output: Error: Call to undefined function unset()

    This example is also available in a playground: https://fiddle.nette.org/latte/#99380fd058

    Expected Behavior

    unset() function call should be possible in a {php} block without importing the "Raw PHP" extension.

    opened by janchaloupka 0
  • Setting ContentType in a context filter does not work in a XML template

    Setting ContentType in a context filter does not work in a XML template

    Version: 3.0.4

    Bug Description

    I have encountered a problem when I was creating a custom filter for XML files that escapes content in a CDATA block.

    The problem is that I need the Latte parser to treat the returned value of this filter as already escaped (safe) text. In a HTML template everything works as expected. however when I use this filter in a XML template (specified using a {contentType xml} block), the returned value is always escaped by Latte no matter the selected ContentType in the filter.

    Steps To Reproduce

    $latte = new \Latte\Engine();
    $latte->addFilter('cdata', static function(\Latte\Runtime\FilterInfo $info, string $data) {
        $info->contentType = \Latte\ContentType::Xml; // Same result with any other ContentType
        return '<![CDATA[' . $data . ']]>';
    });
    
    $latte->setLoader(new \Latte\Loaders\StringLoader(['template' => '{contentType xml}<?xml version="1.0"?><root>{$foo|cdata}</root>']));
    $latte->render('template', ['foo' => '<a>Hello world</a>']);
    

    Output: <?xml version="1.0"?><root>&lt;![CDATA[&lt;a&gt;Hello world&lt;/a&gt;]]&gt;</root> (incorrect)

    Expected: <?xml version="1.0"?><root><![CDATA[<a>Hello world</a>]]></root>

    Expected Behavior

    When used in a HTML template, everything works as expected. I would expect the same behaviour for XML templates.

    $latte = new \Latte\Engine();
    $latte->addFilter('cdata', static function(\Latte\Runtime\FilterInfo $info, string $data) {
        $info->contentType = \Latte\ContentType::Html;
        return '<![CDATA[' . $data . ']]>';
    });
    
    $latte->setLoader(new \Latte\Loaders\StringLoader(['template' => '<root>{$foo|cdata}</root>']));
    $latte->render('template', ['foo' => '<a>Hello world</a>']); // This returns the expected result
    
    opened by janchaloupka 0
  • Add namespace loader

    Add namespace loader

    • bug fix / new feature? #314
    • BC break? no
    • doc PR: nette/docs#???

    This introduce a namespace loader. This allows to use a namespace like app:: similar to Plates and Blade template engine.

    Usage:

        $defaultLoader = new FileLoader(__DIR__ . '/templates');
        $emailLoader = new FileLoader(__DIR__ . '/emails');
        $someLoader = new FileLoader(__DIR__ . '/vendor/some/some/templates');
        $otherLoader = new StringLoader(['main' => 'othercontent']);
    
        $loader = new NamespaceLoader([
            '' => $defaultLoader,
            'email' => $emailLoader,
            'some' => $someLoader,
            'other' => $otherLoader,
        ]);
    

    This way it is very flexible as we can use any loader again.

    opened by alexander-schranz 1
  • [Feature Request] Support for multiple paths

    [Feature Request] Support for multiple paths

    Currently Latte does only provide a FileLoader which allows to bind templates to a single directory.

    I think it would be great if Latte would provide additional Loader which allows to register multiple directories via a namespace. So Latte could example use in Laminas Framework as Renderer also by multiple Modules which could registering additional paths.

    In twig example the loader accepts multiple paths and the loader will then look one after the other directory.

    Another possibility in twig is a namespace example I can have @app/test.html.twig and @other/test.html.twig and register paths like:

    [
        'app' => __DIR__ . '/templates',
        'other' => __DIR__ . '/vendor/other/module/templates',
    ]
    

    While in twig the @ symbol is used, I found while working on my abstraction that other frameworks use the :: syntax for this e.g. other::blade.

    A implementation could look like the following:

    MultiPathLoader.php
    <?php
    
    /**
     * This file is part of the Latte (https://latte.nette.org)
     * Copyright (c) 2008 David Grudl (https://davidgrudl.com)
     */
    
    declare(strict_types=1);
    
    namespace Latte\Loaders;
    
    use Latte;
    
    
    /**
     * Template loader.
     */
    class MultiPathLoader implements Latte\Loader
    {
        private array $loaders = [];
    
    
    	public function __construct(?array $baseDirs = ['' => null])
    	{
            foreach ($baseDirs as $key => $baseDir) {
                $this->loaders[$key] = new FileLoader($baseDir);
            }
    	}
    
    
    	/**
    	 * Returns template source code.
    	 */
    	public function getContent(string $name): string
    	{
            [$loader, $name] = $this->extractLoaderAndName($name);
    
            return $loader->isExpired($name, $name);
    	}
    
    
    	public function isExpired(string $file, int $time): bool
    	{
            [$loader, $name] = $this->extractLoaderAndName($file);
    
            return $loader->isExpired($name, $time);
    	}
    
    
    	/**
    	 * Returns referred template name.
    	 */
    	public function getReferredName(string $name, string $referringName): string
    	{
            [$loader, $name] = $this->extractLoaderAndName($name);
    
            return $loader->getReferredName($name, $referringName);
    	}
    
    
    	/**
    	 * Returns unique identifier for caching.
    	 */
    	public function getUniqueId(string $name): string
    	{
            [$loader, $name] = $this->extractLoaderAndName($name);
    
            return $loader->getUniqueId($name);
    	}
    
    
        private function extractLoaderAndName(string $name): array
        {
            if (\str_starts_with('@', $name)) {
                // the `@module/template` syntax
                [$loaderKey, $fileName] = \explode('/', substr($name, 1), 2);
                // alternative `module::template` syntax
                [$loaderKey, $fileName] = \explode('::', $name, 2);
    
                return [
                    $this->loaders[$loaderKey],
                    $fileName,
                ];
            }
    
            return [
                $this->loaders[''],
                $name,
            ];
        }
    }
    

    What do you think about this. Is this a Loader which you think make sense to live inside Latte Core and you are open for a pull request for it?

    opened by alexander-schranz 5
  • Html object is escaped when used to output string inside an HTML tag

    Html object is escaped when used to output string inside an HTML tag

    Bug Description

    As discussed in this thread it is not possible to return markup that does not need the |noescape filter when used inside an HTML tag.

    Description of the alfred() method: https://github.com/baumrock/RockFrontend/blob/82a6a33592dc0831cd0b435bf94d507e39f7c6ac/RockFrontend.module.php#L200-L222

    Expected Behavior

    <div {alfred($page)}>some markup</div>
    

    This should add a custom html string like alfred='{some json here}' that is not escaped so that the resulting html can be read by JS

    <div alfred='{some json here}'>some markup</div>
    

    Current state

    At the moment I have to use the |noescape filter all the time when using alfred():

    <div {alfred($page)|noescape}>some markup</div>
    

    PS: Not sure if that is a bug or a feature request, sorry if I picked the wrong one!

    opened by BernhardBaumrock 2
Releases(v3.0.5-RC)
  • v3.0.5-RC(Dec 28, 2022)

    • added TracyExtension
    • Node: getIterator() must exists (BC break)
    • implemented mandatory escaping (cannot be disabled using |noescape)
    • In <script type=unknown> are not escaped HTML
    • In <script type=text/html> are escaped every HTML characters
    • Escaper: added new state HtmlRawText
    • Better detection of attribute type in <script> and <style>
    • Escaper: added HtmlAttributeQuoted & HtmlAttributeUnquoted
    • Content type 'text' uses escaping
    • Parser: fixed support for dash in n:attributes
    Source code(tar.gz)
    Source code(zip)
  • v3.0.4(Nov 4, 2022)

    • TemplateLexer: fixed tag parsing & double syntax
    • removed Engine::$probe
    • Extension::beforeRender() changed signature (BC break!!)
    • Extension::beforeRender() is called just before the template is rendered
    • Engine, Template: provides are stored in stdClass
    Source code(tar.gz)
    Source code(zip)
  • v2.11.6(Nov 4, 2022)

  • v3.0.3(Sep 12, 2022)

    • support for PHP 8.2
    • Blueprint: fixed compatibility with PhpGenerator v4
    • TemplateLexer: fixed unterminated tags handling
    • improved error messages
    • 'on line 1' is printed in exception messages
    Source code(tar.gz)
    Source code(zip)
  • v3.0.2(Jun 15, 2022)

    • improved {syntax} and n:syntax handling
    • Parser: added support for HEREDOC/NOWDOC
    • {_...} accepts unquoted string
    • some changes in AST nodes structure (BC break)
    • some fixes
    Source code(tar.gz)
    Source code(zip)
  • v3.0.1(Jun 1, 2022)

    • some fixes
    • added TranslatorExtension, reverts "translate moved to nette/application"
    • {_var} and {translate} can translate values during compile time
    • {translate} accepts parameters
    • SafeUrl is not used for attribute parts
    • Fixed {first} inside n:foreach #298
    • Engine::isExpired() checks filemtime of extensions
    • Linter: accepts file name or mask
    • Linter: initalization moved to class
    • Linter: prints summary
    • ParametersNode & DefineNode parses parameters as ParameterNode[] with full type
    Source code(tar.gz)
    Source code(zip)
  • v2.11.4(May 30, 2022)

  • v3.0.0(May 17, 2022)

  • v2.11.3(May 8, 2022)

    • SecurityPolicy::allowMacros() is deprecated, use allowTags()
    • Notice: Engine::addFilter(null, ...) is deprecated, use addFilterLoader()
    • PhpWriter::formatWord() accepts PascalCase class constants
    • Notice: missing </script> or </style>
    • Case sensitive filter |checkUrl
    • Linter: add class Latte\Tools\Linter #286
    • BlueScreenPanel: fixed compatibility with Tracy 2.9
    Source code(tar.gz)
    Source code(zip)
  • v2.11.2(Apr 20, 2022)

  • v2.11.1(Apr 7, 2022)

    This version is for easy transition to the upcoming Latte 3. It notes all deprecated things that will be removed in the next version.

    • Case sensitive filters: notice when case does not match
    • Notice: Filter |noescape should be placed at the very end in modifier
    • sandbox: operator new is forbidden in {var} & {parameters}
    • Notice: Change {include "inc"} to {include file "inc"} for clarity
    • Notice: expressions like {block foo-$var} should be put in double quotes
    • n-ifcontent on empty element in deprecated
    • Template::getParameter() is deprecated
    • added {translate} as pair replacement for {_}
    Source code(tar.gz)
    Source code(zip)
  • v2.11.0(Feb 22, 2022)

    This version is for easy transition to the upcoming Latte 3. It notes all deprecated things that will be removed in the next version.

    • throws exception when template contains control characters
    • removed support for deprecated optional chaing expressions like $foo->prop? or $foo->call()?
    • empty closing tag {/} is deprecated
    • Colons as argument separator in modifiers are deprecated, ie replace {$foo|filter:1:2:3} with {$foo|filter:1, 2, 3}
    • Auto-empty is deprecated (it affects {label}, must be written as {label /} or pair {label}...{/label}
    • Filters::escapeJS: invalid UTF-8 is replaced with Unicode Replacement Character U+FFFD (instead of throwing exception)
    • Template: removed old accumulators $_l, $_g (deprecated in 2.4, BC break)
    • Tag name cannot start with ?
    • n:tag- & n:inner- is deprecated on void elements
    • deprecated n:inner-snippet
    • {includeblock} is deprecated
    • Auto-empty is deprecated
    • allow use of text/plain for script tag (#282)
    Source code(tar.gz)
    Source code(zip)
  • v2.10.9(Feb 22, 2022)

    • added & improved tests
    • Compiler: checks macro is not in quotes in JS even with |noescape
    • Engine: in sandboxed mode is policy mandatory
    • CompileException: added line number
    • Revert "TokenIterator: fixes, sync with nette/tokenizer"
    • implode() with 2 arguments
    • output buffering uses try/finally
    • PhpHelpers::reformatCode() places elseif/catch/finally after }
    • PhpHelpers::reformatCode() refactoring $next
    • {try} and {else} are not replaced
    Source code(tar.gz)
    Source code(zip)
  • v2.10.8(Jan 4, 2022)

  • v2.9.6(Jan 4, 2022)

  • v2.9.5(Nov 26, 2021)

    This is a security release.

    • Parser: removes all control characters from template [Closes #279]
    • PhpWriter::removeCommentsPass() replaces comment with space to prevent unwanted result
    • isNext() a isPrev() called with SIGNIFICANT
    Source code(tar.gz)
    Source code(zip)
  • v2.8.8(Jan 4, 2022)

  • v2.8.7(Nov 26, 2021)

    This is a security release.

    • Parser: removes all control characters from template [Closes #279]
    • PhpWriter::removeCommentsPass() replaces comment with space to prevent unwanted result
    • isNext() a isPrev() called with SIGNIFICANT
    Source code(tar.gz)
    Source code(zip)
  • v2.10.7(Dec 21, 2021)

    • BlueScreenPanel: added source mapping & files creating in Tracy 2.9
    • Filters: better error message for deprecated strftime()
    • improved coding standard
    Source code(tar.gz)
    Source code(zip)
  • v2.10.6(Nov 26, 2021)

    This is a security release.

    • Parser: removes all control characters from template [Closes #279]
    • PhpWriter::removeCommentsPass() replaces comment with space to prevent unwanted result
    • isNext() a isPrev() called with SIGNIFICANT
    • linter: warn on BOM and deprecated stuff
    • linter: Add shebang so it can be run directly (#277)
    Source code(tar.gz)
    Source code(zip)
  • v2.8.6(Oct 27, 2021)

    This is a security release.

    • Blueprint: fixed compatibility with PhpGenerator 3.6.2
    • Filters: compatibility with JS binding II.
    • // comment and # comment are forbidden inside tags
    • {capture} creates Html object only in HTML-TEXT context
    • {_translate} & {capture} passes full content-type to FilterInfo
    • {block|filter} passes full content-type to FilterInfo
    Source code(tar.gz)
    Source code(zip)
  • v2.9.4(Oct 27, 2021)

    This is a security release.

    • Blueprint: fixed compatibility with PhpGenerator 3.6.2
    • Filters: compatibility with JS binding II.
    • // comment and # comment are forbidden inside tags
    Source code(tar.gz)
    Source code(zip)
  • v2.7.4(Oct 27, 2021)

    • Blueprint: fixed compatibility with PhpGenerator 3.6.2
    • Filters: compatibility with JS binding II.
    • // comment and # comment are forbidden inside tags

    Branch 2.7 is no longer supported

    Source code(tar.gz)
    Source code(zip)
  • v2.6.4(Oct 27, 2021)

  • v2.5.7(Oct 27, 2021)

  • v2.10.5(Oct 27, 2021)

    • added Latte linter
    • Filters: compatibility with JS binding II.
    • {ifset block, block} fixed
    • // comment and # comment are forbidden inside tags
    • Blueprint: used syntax highlighter
    • Template::getParameters() skips $_l $_g
    Source code(tar.gz)
    Source code(zip)
  • v2.10.4(Oct 2, 2021)

    • support for PHP 8.1
    • Blueprint: skips $_l $_g
    • BlockMacros: removed unnecesary $ʟ_nm (after f88777b0799)
    • BlockMacros: filters are not allowed in {include parent}
    • BlockMacros: fixed {include this} in {block}
    • LattePanel: removed dependency on Nette #274
    • PhpWriter::quotingPass supports 'default =>' syntax #267
    • Filters: flipepd string/array tests
    • CoreMacros: Arguments are not allowed in n:ifcontent
    Source code(tar.gz)
    Source code(zip)
  • v2.9.3(Feb 24, 2021)

    • {capture} creates Html object only in HTML-TEXT context
    • {_translate} & {capture} passes full content-type to FilterInfo
    • {block|filter} passes full content-type to FilterInfo
    • Policy::createSafePolicy() added new function
    • Filters: escapes JS binding {{ }}
    • allows {else} in {first}, {last}, {sep} #247
    • BlockMacros: removed empty line after {define} #251
    • {embed}: reimplemented, creates new block layers dynamically, uses stack
    Source code(tar.gz)
    Source code(zip)
  • v2.7.3(Feb 24, 2021)

    • {capture} creates Html object only in HTML-TEXT context
    • {_translate} & {capture} passes full content-type to FilterInfo
    • {block|filter} passes full content-type to FilterInfo
    • Filters: escapes JS binding {{ }}
    Source code(tar.gz)
    Source code(zip)
  • v2.6.3(Feb 24, 2021)

    • {capture} creates Html object only in HTML-TEXT context
    • {_translate} & {capture} passes full content-type to FilterInfo
    • {block|filter} passes full content-type to FilterInfo
    • Filters: escapes JS binding {{ }}
    Source code(tar.gz)
    Source code(zip)
Owner
Nette Foundation
Nette Foundation
PHP Template Attribute Language — template engine for XSS-proof well-formed XHTML and HTML5 pages

PHPTAL - Template Attribute Language for PHP Requirements If you want to use the builtin internationalisation system (I18N), the php-gettext extension

PHPTAL 175 Dec 13, 2022
⚡️ Simple and fastly template engine for PHP

EasyTpl ⚡️ Simple and fastly template engine for PHP Features It's simple, lightweight and fastly. No learning costs, syntax like PHP template It is s

PHPPkg 19 Dec 9, 2022
PHP template engine for native PHP templates

FOIL PHP template engine, for PHP templates. Foil brings all the flexibility and power of modern template engines to native PHP templates. Write simpl

Foil PHP 167 Dec 3, 2022
Smarty is a template engine for PHP, facilitating the separation of presentation (HTML/CSS) from application logic.

Smarty 3 template engine smarty.net Documentation For documentation see www.smarty.net/docs/en/ Requirements Smarty can be run with PHP 5.2 to PHP 7.4

Smarty PHP Template Engine 2.1k Jan 1, 2023
View template engine of PHP extracted from Laravel

Blade 【简体中文】 This is a view templating engine which is extracted from Laravel. It's independent without relying on Laravel's Container or any others.

刘小乐 143 Dec 13, 2022
Twig Template Engine to Phalcon PHP

Twig Template Engine to Phalcon PHP

Vinicius 4 Oct 7, 2022
Liquid template engine for PHP

Liquid is a PHP port of the Liquid template engine for Ruby, which was written by Tobias Lutke. Although there are many other templating engines for PHP, including Smarty (from which Liquid was partially inspired)

Harald Hanek 230 Aug 18, 2022
Pug (Jade) template engine for Symfony

Pug-Symfony Pug template engine for Symfony This is the documentation for the ongoing version 3.0. Click here to load the documentation for 2.8 Instal

Pug PHP 41 Dec 16, 2022
PHPlater, a simple template engine.

PHPlater A simple PHP template engine that lets PHP do all the logic and then append it to the HTML in the template file. It is set to solve the probl

John Larsen 2 Jun 3, 2022
A template abstraction prototype for PHP template engines

Schranz Templating A template abstraction prototype for PHP template engines. This project should help to find a way for a general Template Render Int

Schranz Templating 16 Dec 7, 2022
Standalone Skeltch templating engine for PHP

SkeltchGo is a standalone version of Glowie Skeltch templating engine for PHP, intented to use from outside the framework.

glowie 1 Nov 5, 2021
A PHP project template with PHP 8.1, Laminas Framework and Doctrine

A PHP project template with PHP 8.1, Laminas Framework and Doctrine

Henrik Thesing 3 Mar 8, 2022
SwitchBlade: Custom Directives for the Laravel Blade templating engine

SwitchBlade: Custom Directives for the Laravel Blade templating engine

Awkward Ideas 10 Nov 29, 2022
Experimental ActiveRecord layer on top of Doctrine2 using the Twig templating engine

This is an experiment for building ActiveRecord functionality on top of Doctrine2 using the Twig templating engine. Whether it is called Propel2 or not is irrelevant.

Francois Zaninotto 85 Dec 5, 2022
A complete and fully-functional implementation of the Jade template language for PHP

Tale Jade for PHP Finally a fully-functional, complete and clean port of the Jade language to PHP — Abraham Lincoln The Tale Jade Template Engine brin

Talesoft 91 Dec 27, 2022
The free-to-use template for your Imagehost-website made with PHP, HTML and CSS!

The free-to-use template for your Imagehost-website made with PHP, HTML and CSS! Some information before we start This repo is only code related, to a

Ilian 6 Jul 22, 2022
The free-to-use template for your Imagehost-website made with PHP, HTML and CSS!

The free-to-use template for your Imagehost-website made with PHP, HTML and CSS! Some information before we start This repo is only code related, to a

Ilian 6 Jul 22, 2022
Foil brings all the flexibility and power of modern template engines to native PHP templates

Foil brings all the flexibility and power of modern template engines to native PHP templates. Write simple, clean and concise templates with nothing more than PHP.

Foil PHP 167 Dec 3, 2022
A SilverStripe Module with template methods to quickly make use of FocusPoint, LazySizes, and Object-fit

LazyFocusFit A SilverStripe module with template methods to quickly make use of FocusPoint, LazySizes and object-fit. Requirements PHP FocusPoint JS/C

Evans Hunt 9 Nov 4, 2022