Smarty templates
Building HTML from strings in PHP is awkward and unsafe. In the PrestaShop front office markup is written in Smarty templates — .tpl files where HTML is mixed with simple commands in curly braces. A module passes variables to a template and gets a ready string back.
Where templates live
Module templates are kept in the views/templates folder, templates for hooks in the hook subfolder. Create the file by hand:
modules/acmehello/
├── acmehello.php
├── logo.png
└── views/
└── templates/
└── hook/
└── home.tplThe first template
{* modules/acmehello/views/templates/hook/home.tpl *}
<section class="acme-hello">
<h2>{$greeting}</h2>
</section>The line in {* *} is a Smarty comment; it does not reach the page. {$greeting} prints a variable.
Rendering the template from a hook
// modules/acmehello/acmehello.php
public function hookDisplayHome(array $params): string
{
$this->context->smarty->assign([
'greeting' => 'Welcome!',
]);
return $this->fetch('module:acmehello/views/templates/hook/home.tpl');
}assign() passes variables to the template, fetch() returns the ready HTML. Refresh the home page: the greeting is now inside an h2 heading. There is no need to reset the module — the hook subscription has not changed.
The path starts with module: and the module name. Thanks to that a theme can replace the template with its own — see the lesson on themes.
Conditions and loops
Create a second template — for the reviews block on the product page:
{* modules/acmehello/views/templates/hook/product-reviews.tpl *}
<section class="acme-reviews">
<h3>Reviews</h3>
{if $reviews}
<ul>
{foreach from=$reviews item=review}
<li>{$review.author}: {$review.rating} out of 5</li>
{/foreach}
</ul>
{else}
<p>No reviews yet.</p>
{/if}
</section>// modules/acmehello/acmehello.php
public function hookDisplayFooterProduct(array $params): string
{
$this->context->smarty->assign([
'reviews' => [
['author' => 'Anna', 'rating' => 5],
['author' => 'Boris', 'rating' => 4],
],
]);
return $this->fetch('module:acmehello/views/templates/hook/product-reviews.tpl');
}A list of two reviews appears on the page of any product. For now they are a plain array; in the database lesson a query replaces it.
Syntax
| Notation | What it does |
|---|---|
{$name} | Prints a variable |
{$review.author} | An array element or a property |
{if $a > 1}...{elseif ...}...{else}...{/if} | A condition |
{foreach from=$list item=row}...{foreachelse}...{/foreach} | A loop; foreachelse runs when the list is empty |
{$text|truncate:100} | A modifier: cut to 100 characters |
{$price|string_format:'%.2f'} | A modifier: number format |
{include file='module:acmehello/views/templates/hook/stars.tpl'} | Insert another template |
Ready variables
In the front office every template already has engine variables: $customer, $cart, $currency, $language, $shop, $urls.
{if $customer.is_logged}
<p>Hello, {$customer.firstname}!</p>
{else}
<a href="{$urls.pages.authentication}">Sign in</a>
{/if}A logged-in customer sees their name, a guest sees a link to the sign-in page.
Escaping
In the front office Smarty is configured so that every variable is escaped on output: the < and > characters turn into safe codes, and a review text cannot become a script. To print a variable as HTML, add nofilter:
<div class="acme-text">{$html nofilter}</div>Only HTML prepared by you or by the core is printed this way. Back office templates have no automatic escaping — there you write {$name|escape:'html':'UTF-8'}. In other people's modules this modifier is also seen in the front office: it is harmless there.
Common mistake
A review text shows up with visible tags, and to "fix" it nofilter is added to the variable. Now a review containing a script runs for every visitor — an XSS vulnerability. nofilter is acceptable only for content that was not entered by a user or that has been sanitised.