This functionality is only available since version 2.1

When you create a module for Thelia, you might want it to extend the site in 2 ways:

  • The heart of Thelia to generate new behaviors, such as sending an email to the administrator when a product is no longer available. On Thelia, this functionality is managed by the Events Listener/Dispatcher.
  • Changing the display of some area of pages. You can control this with Hooks.

The Hooks are actually entry points in the templates at which modules can insert their own code, add new features and change the appearance of the site.

Before Hooks, the only way to change the display of the site was to change the template of the site to include new pieces of code to interact with the module. This solution was not ideal, since it required some integration work, could be quite complex for beginners, and introduced the possibility of overwriting all customized template code in an update to the default template.

Hooks simplify this kind of integration. Modules that extend hooks provide a default implementation based on the default Thelia template. If your template code uses a given hook in the same way as the default template, activating a module that extends that hook should “just work.”

The great strength of Thelia is the possibility to customize the display of the site. But it is possible that some modules do not integrate in an optimal way with their default implementation. Fortunately, Thelia provides a way to override this from the template, without having to touch module files, making ongoing module updates possible.

How hooks work

The principle of hooks is simple. The template includes a hook function or block in the Smarty code. When Smarty parses this function/block an event is created and dispatched to methods of modules that listen to this hook event.

The modules that listen for these events generate content and add it directly to the event. When the event is finished, the content is injected in place of the function/block as rendered markup.

There are two distinct types of hooks you can extend: the basic hook function and the hookblock construct.

The hook function

The smarty function {hook name="hookname" ... } injects the code generated during the event propagation. The fragments of HTML code generated in modules are simply concatenated to the markup that the event will render.

These hooks pass a Thelia\Core\Event\Hook\HookRenderEvent object to the registered event listeners.

Example of a hook function:

In this example, the hook code is product.details.top and the code generated by modules listening to this hook is concatenated and injected here.

Notice: you can add arguments to the smarty function (or block) to help identify the context of the request. Here, we have the product id that allows you to perform test on this product and display informations related to the current product.

...
<section id="product-details">
    {hook name="product.details.top" product="{$ID}"}
...
</section>

The hook block

The Smarty construct {hookblock name="hookname" ... }...{/hookblock} works in concert with the {forhook rel="hookname" ... }{/forhook} construct and allows you to iterate over any number of fragments that your modules have generated and added to the hook via the HookRenderBLockEvent API.

These fragments are just associative arrays (hash tables). forhook iterates through these fragment arrays, mapping their keys to smarty variables that you can use in your template, just as you would pass an associative array of template variables when rendering a normal Smarty template file.

This type of hook is useful when you want to respect the layout of the template and just add the relevant informations from your module. For example, if you have a list of block in the sidebar that have the same appearence (title bar, a content, a link) and want to add your block. Your modules can extends this hook by listening to this hook and add a fragment array with title, content and link data to the passed-in event object.

These hooks pass a Thelia\Core\Event\Hook\HookRenderBlockEvent object to the registered event listeners.

Example of a hook block:

The Smarty block is a little bit more complex than the hook construct, but more flexible. The hook provides an array of arrays to Smarty: each inner array is an associative array and can be thought of as a “row” where each row has its own value for each “column.” The forhook block allows you to iterate on each row and render its variables (its “columns”) arbitrarily. The variables may be different depending on the hook. To find out which variables are supported for a given hook, refer to the documentation of that hook.

...
<section id="product-tabs">
    {hookblock name="product.additional" product="{product attr="id"}"}
    <ul class="nav nav-tabs" role="tablist">
        <li class="active" role="presentation"><a id="tab1" href="#description" data-toggle="tab" role="tab">{intl l="Description"}</a></li>

        {forhook rel="product.additional"}
            <li role="presentation"><a id="tab{$id}" href="#{$id}" data-toggle="tab" role="tab">{$title}</a></li>
        {/forhook}

    </ul>
    <div class="tab-content">
        <div class="tab-pane active in" id="description" itemprop="description" role="tabpanel" aria-labelledby="tab1">
            <p>{$DESCRIPTION|default:'N/A' nofilter}</p>
        </div>

        {forhook rel="product.additional"}
        <div class="tab-pane" id="{$id}" role="tabpanel" aria-labelledby="tab{$id}">
            {$content nofilter}
        </div>
        {/forhook}

    </div>
    {/hookblock}
</section>
...

Note the three Smarty variables rendered in the two forhook constructs above: $id, $title, and $content. Again, these depend on the hook. There is nothing special about these three variables in the broader context of this template; they just happen to be required from any module that extends the product.additional hook.

Common behaviors between function and block

Atrributes

module

Some hooks are designed to be called for only a specific module, using the module attribute. In fact it’s just used by delivery and payment modules. Thus, you can call this hook for the delivery and the payment module that have been selected for the current order and not for all modules.

For example:

{* delivery module can customize the delivery address *}
{hook name="order-invoice.delivery-address" module="{order attr="delivery_module"}"}

In place of the module argument you can use the modulecode argument which behave the same way but accept a module code instead of a module id.

location

This argument exists only for backwards compatibility with the module_include function used in the backOffice template in Thelia version prior to the version 2.1.

For example:

{hook name="customer.top" location="customer_top" }

First, modules that are attached to customer.hook hook will be called. Then old {module_include location="customer_top"} will be appended to the event (in fact all templates in {modules}/AdminIncludes/customer_top.html).

others

All other attributes that are used in the hook block/function will be added to the event propagated for this hook and could be used to precise the context of the hook (the id of the current product, …)

ifhook / elsehook

As with ifloop/elseloop, Thelia provides ifhook/elsehook constructs to test whether any content has been generated for the corresponding event.

The behavior is similar and you have to use the rel attribute to specify the corresponding hook:

{loop name="order" type="order" customer="current" id="$order_id" limit="1" }
    ...
    {ifhook rel="account-order.delivery-information"}
        {hook name="account-order.delivery-information" module={$delivery_id} order={$order_id}}
    {/ifhook}
    {elsehook rel="account-order.delivery-information"}
        <p>{loop name="delivery-module" type="module" id=$DELIVERY_MODULE}{$TITLE}{/loop}</p>
    {/elsehook}
    ...
{/loop}

Here, in the elsehook clause, $DELIVERY_MODULE and $TITLE must come from the scope of the surrounding loop, since if the elsehook code is ever reached, it means by definition that no event-specific variables such as $delivery_id are available.