Creating Modules

PrestaShop has a very flexible module system that can be used to add functionality to PrestaShop without modifying any of its core files. Much of PrestaShop's core functionality is written using modules, which minimises the size of PrestaShop's core. Unneeded modules can be deleted to save space on the server.

Creating the module files

The first step to creating a module is coming up with a unique directory name for the module. It is convention for a module's directory name to be all lowercase letters and have one of the following prefixes:

Prefix Description
block Used for modules that appear as a block in the left or right columns.
graph Used for graphing modules on the Statistics tab.
grid Used for grid modules on the Statistics tab.
home Used for modules that appear in the centre column on the homepage.
product Used for modules that appear on the product page
stats Used for statistics modules on the Statistics tab.

If the module doesn't fit any of these categories, then no prefix is used. One way to ensure the module name is unique is to use a vendor suffix. For example, Nethercott Constructions uses the nc suffix on its module names to ensure they are unique.

Create a directory in the modules directory with the name you selected. Next, create a file in the new directory with the same name as the directory followed by .php. For example, if the name of your module is helloworldnc, then create a file helloworldnc.php in the helloworldnc directory.

In that file, copy the required if statement from the top of another module, then add a new class with the same name as the file that extends the Module class. The case of the class name isn't important, though it is convention to use an uppercase letter at the start of each word. Here's an example:

<?php

if (!defined('_PS_VERSION_'))
    exit;
class HelloWorldNC extends Module
{

}

?>

The module will then appear on the Modules tab in the Other Modules section.

Adding the constructor

Although the module will now appear in the Other Modules section on the Modules tab, it will not display a name or description. This information needs to be added to a constructor inside the class. Here's an example constructor:

public function __construct()
{
    $this->name = 'helloworldnc';
    $this->tab = 'front_office_features';
    $this->version = '1.0';
    $this->author = 'Nethercott Constructions';

    parent::__construct();

    $this->displayName = $this->l('Hello World');
    $this->description = $this->l('This is an example module');
    $this->confirmUninstall = $this->l('Are you sure you want to uninstall?');
    if (!Configuration::get('HELLO_WORLD_NC_NAME'))
        $this->warning = $this->l('No name provided');
}

The name variable must be the directory name you chose for the module. The tab variable is used to select which section the module appears in. It must be one of the following values:

Tab Value Tab Name
administration Administration
advertising_marketing Advertising & Marketing
analytics_stats Analytics & Stats
billing_invoicing Billing & Invoicing
checkout Checkout
content_management Content Management
export Export
front_office_features Front Office Features
i18n_localization I18n & Localization
market_place Market Place
merchandizing Merchandizing
migration_tools Migration Tools
others Other Modules
payments_gateways Payments & Gateways
payment_security Payment Security
pricing_promotion Pricing & Promotion
quick_bulk_update Quick / Bulk update
search_filter Search & Filter
seo SEO
shipping_logistics Shipping & Logistics
slideshows Slideshows
smart_shopping Smart Shopping
social_networks Social Networks

Any value other than the ones above will place the module in the Other Modules section.

The version variable can be any number you like. It is used to make it easy to identify which version of the module you are using. It is convention to use numbers below 1.0 before all the planned features have been added to the module and then increment the value as bugs are fixed and more features are added.

The author variable is the author name displayed after by in the module listings. It can also be used to filter modules by author. Next, the parent constructor is called, which sets the name variable to the module's ID if no name was provided, adds the module to the cache and creates a _path variable with the module's path.

The displayName variable must be the module name that is displayed in bold on the Modules tab and the description variable must be the description of the module displayed below the module's name.

The confirmUninstall variable is an optional variable that specifies the confirmation message to display before uninstalling the module. If uninstalling the module will cause important information to be lost, then it is a good idea to add a confirmation message.

The warning variable is another optional variable that specifies the warning message to display at the top of the Modules tab. If the module requires information to be entered before it can function correctly, then it is a good idea to check whether it has been set and display a warning if it hasn't.

A module should have a 16 x 16 pixel GIF image called logo.gif and a 32 x 32 pixel PNG image called logo.png inside its directory. The PNG image is used in the Normal view and the GIF image is used in the Favorites view on the Modules tab.

PrestaShop v1.6 uses the free FatCow icon set here, so check whether there is an unused icon from the set that matches the module. The module should then display on the Modules tab like this:

PrestaShop v1.5.x module listing

PrestaShop v1.5.x module listing

Adding the config.xml file

Next, add a config.xml file to the module. Although one isn't required, it helps to reduce the amount of memory required by the Modules tab by loading only the file instead of the entire module to get the module's name and description. Here is an example config.xml file:

<?xml version="1.0" encoding="UTF-8" ?>
<module>
    <name><![CDATA[helloworldnc]]></name>
    <displayName><![CDATA[Hello World]]></displayName>
    <version><![CDATA[1.0]]></version>
    <author><![CDATA[Nethercott Constructions]]></author>
    <description><![CDATA[This is an example module]]></description>
    <tab><![CDATA[front_office_features]]></tab>
    <is_configurable>1</is_configurable>
    <need_instance>1</need_instance>
    <limited_countries></limited_countries>
</module>

The first line is used to define the file as an XML file with UTF-8 encoding. The rest of the file creates a module object with a number of variables. The values of most variables are wrapped in <![CDATA[ ]]> to prevent the values being parsed as XML.

The name variable is the directory name of the module. It is important for this value to match the module's directory, otherwise the logo will not display and none of the module links will work.

The displayName, version, author, description and tab variables should be the same as the module display name, version, description and tab specified in the module's code. It is best that they match the ones in the module, though no errors will occur if they don't.

The is_configurable variable is used to indicate whether the module has a configuration page. If it does, set the variable to 1, otherwise set it to 0. It is important for this value to be correct, otherwise the Configure link may be missing from the module, making it impossible to access the configuration page, or cause an error when it is clicked and no configuration page actually exists.

The need_instance variable is used to indicate whether an instance of the module needs to be created when the Modules tab is loaded. If the value is 0, then the module will not be loaded, which will save time and memory. If the value is 1, then the module will be loaded. If the module may need to display a warning message on the Modules tab, then choose the value 1. Otherwise, choose 0 to save time and memory.

The limited_countries variable can optionally be used if a module should only be available in a specific country. For example, the following line uses the country ISO code to limit a module to France only:

<limited_countries>fr</limited_countries>

Adding the install and uninstall functions

Although the module is now displayed on the Modules tab, it can't be installed yet. An install function needs to be added inside the class before it can be installed. Here's an example install function:

public function install()
{
    if (!parent::install() OR !$this->registerHook('displayLeftColumn') OR 
        !$this->registerHook('displayHeader') OR
        !Configuration::updateValue('HELLO_WORLD_NC_SHOW_NAME', 1) OR
        !Configuration::updateValue('HELLO_WORLD_NC_NAME', 'World'))
        return false;
    return true;
}

The install function uses an if statement to call the parent install function, place the module in left column and header hooks, and set variables to their default values. If any of these return false, then the install function will return false, which causes PrestaShop to display the following error message:

PrestaShop v1.5 Module install error

PrestaShop v1.5 Module install error

If they all return true, then the install function will return true, which causes PrestaShop to display the following message:

PrestaShop v1.5 Module installed successfully

PrestaShop v1.5 Module installed successfully

It is also a good idea to add an uninstall function, so that all the module's settings are removed from the database when the module is not being used. Here's an example uninstall function:

public function uninstall()
{
    if (!parent::uninstall() OR
        !Configuration::deleteByName('HELLO_WORLD_NC_SHOW_NAME') OR
        !Configuration::deleteByName('HELLO_WORLD_NC_NAME'))
        return false;
    return true;
}

The uninstall function uses an if statement to call the parent uninstall function and delete all configuration values related to the module from the database. Like the install function, the uninstall function returns false if any of these return false or returns true otherwise, which causes messages similar to the ones above to be displayed.

It is convention for configuration names to be all uppercase and have an underscore between words. They must also be no more than 32 characters long, otherwise PrestaShop will display an error message. The module can be now be installed and uninstalled.

Adding the configuration page

Although your module can now be installed and uninstalled, it can't be configured. If the module has settings that should be easy to change, a configuration page should be added. The getContent() function is called when the Configure link on a module is clicked.

It displays the name of the module, then calls the displayForm() function to display the configuration page form. Here's an example:

public function getContent()
{
    $output = '<h2>'.$this->displayName.'</h2>';
    return $output.$this->displayForm();
}

public function displayForm()
{
    return '
    <form action="'.$_SERVER['REQUEST_URI'].'" method="post">
        <fieldset>
            <legend><img src="'.$this->_path.'logo.gif" alt="" title="" />'.
                $this->l('Settings').'</legend>

            <label>'.$this->l('Name').'</label>
            <div>
                <input type="text" name="name" value="'.Tools::getValue('name',
                    Configuration::get('HELLO_WORLD_NC_NAME')).'" />
                <p>'.$this->l('The name that will appear after Hello')
                    .'</p>
            </div>

            <label>'.$this->l('Show Name').'</label>
            <div>
                <input type="radio" name="showName" id="showName_on" value="1" '.
                    (Tools::getValue('showName',
                     Configuration::get('HELLO_WORLD_NC_SHOW_NAME')) ?
                     'checked="checked" ' : '').'/>
                <label for="showName_on">
                   <img src="../img/admin/enabled.gif" alt="'.$this->l('Enabled')
                       .'" title="'.$this->l('Enabled').'" />
                </label>
                <input type="radio" name="showName" id="showName_off" value="0" '.
                    (!Tools::getValue('showName',
                     Configuration::get('HELLO_WORLD_NC_SHOW_NAME')) ?
                     'checked="checked" ' : '').'/>
                <label for="showName_off">
                    <img src="../img/admin/disabled.gif" alt="'.
                        $this->l('Disabled').'" title="'.$this->l('Disabled').'" />
                </label>
                <p>'.$this->l('Whether to show the name after Hello')
                    .'</p>
            </div>

            <center><input type="submit" name="submitHelloWorldNC" value="'.
                $this->l('Save').'" /></center>
        </fieldset>
    </form>';
}

This function simply returns a single long text string that contains the HTML of the configuration page. The <form> line is used to post the current configuration settings to the module. If you add a file field as one of the configuration settings, you must change this to the following before the files will be posted:

<form action="'.$_SERVER['REQUEST_URI'].'" method="post"
    enctype="multipart/form-data">

The <fieldset> line is used to create a box around all related configuration settings and the <legend> line is used to add a label to the top of the box to describe the contents. In the example, the label Settings is used along with the module's logo.

The code $this->l('Settings') is used to make the text translatable by selecting Module translations in the dropdown on the Translations subtab of the Tools tab in the Back Office. The <label> lines are used to display the labels of the configuration setting on the left side of the box and the <div> lines display the configuration values on the right side of the box.

The first <input> line creates a text field with a unique name, so it can be identified when it is posted. The value Tools::getValue('name', Configuration::get('HELLO_WORLD_NC_NAME')) makes the text field use the current value of the text field before getting its saved value in the database.

This is important, since it allows the text field to retain its value when an error occurs instead of resetting the text field to the saved value. This allows the user to see what caused the error instead of wonder why the text field didn't appear to save.

The <p> lines display descriptions that make it easier to understand what the configuration options do. The second and third <input> lines are radio buttons that can be used to turn a configuration option on or off. Both of these inputs have the same name showName, which is used to get the selected radio from the posted data.

Each input has a different ID though, which is the name followed by _on for the first input and _off for the second input. The first input has a value of 1 and the second input has a value of 0. After the first input is a green tick image and after the second input is a red cross image.

This makes it clear that the first option turns the configuration setting on and the second radio button turns it off. The images are in <label> tags that link the labels to the images, so that clicking on an icon selects the radio button.

Lastly, the <center> line adds a Save button at the bottom of the box. It has a unique name submitHelloWorldNC and a value of Save, which is displayed as the label on the button. The code styles the button using the current Back Office theme.

There should now be a Configure link on the module like this:

PrestaShop v1.5 Module Configure link

PrestaShop v1.5 Module Configure link

Clicking the Configure link will display the configuration page like this:

PrestaShop v1.5 Module Configuration page

PrestaShop v1.5 Module Configuration page

Saving the configuration page settings

Although there is now a configuration page, none of the settings can be saved. To save settings, more code needs to be added to the getContent() function. Here's an example:

public function getContent()
{
    $this->_errors = array();
    $output = '<h2>'.$this->displayName.'</h2>';
    if (Tools::isSubmit('submitHelloWorldNC'))
    {
        $name = Tools::getValue('name');
        $showName = (int)(Tools::getValue('showName'));
        if ($showName != 0 AND $showName != 1)
            $this->_errors[] = $this->l('Show Name: Invalid choice.');
        if (sizeof($this->_errors) == 0)
        {
            Configuration::updateValue('HELLO_WORLD_NC_NAME', $name);
            Configuration::updateValue('HELLO_WORLD_NC_SHOW_NAME', $showName);
            $output .= $this->displayConfirmation($this->l('Settings updated'));
        }
    }
    else
        $output .= $this->displayErrors();

    return $output.$this->displayForm();
}

This function builds upon the previous function by checking whether data was posted, validating the data, and then displaying an error or success message after the module's name and before the form. The displayErrors() function groups all the error messages into a single box.

The displayConfirmation() function displays a confirmation message in the PrestaShop v1.5 style. It is the first if statement in the getContent() function that checks whether data was posted. The first line in the if statement gets the value of the text field and second line gets the value of the radio buttons.

The radio button value is converted to an integer for data validation purposes. If the radio button value isn't either 0 or 1, then an error message is displayed. This ensures that the setting always has a valid value. If the radio button has a valid value, then the text field and radio button values are written to the database and then a success message is displayed. The module will now save the configuration settings.

Adding the hooks

Although your module now has a fully working configuration page, it doesn't do anything on the website. To make the module do something, you must register hooks the module can be placed in. Here's an example:

public function hookDisplayLeftColumn($params)
{
    $this->context->smarty->assign(array(
        'name' => Configuration::get('HELLO_WORLD_NC_NAME'),
        'showName' => (int)(Configuration::get('HELLO_WORLD_NC_SHOW_NAME')) 
    ));

    return $this->display(__FILE__, 'helloworldnc.tpl');
}

public function hookDisplayRightColumn($params)
{
    return $this->hookLeftColumn($params);
}

public function hookDisplayHeader()
{
    $this->context->controller->addJS($this->_path.'js/helloworldnc.js');
    $this->context->controller->addCSS($this->_path.'css/helloworldnc.css', 'all');
}

The hookDisplayLeftColumn function defines what code should be added when the module is placed in the left column hook. The first line in the function passes variables into the template file.

The next line gets the text field and radio settings from the database and creates Smarty variables. The last line of the function calls the helloworldnc.tpl file in the module's directory, which has access to the Smarty variables that were created.

It then returns the HTML generated by the template, which is displayed in place of the {$HOOK_LEFT_COLUMN} along with the HTML from any other modules that are also in the hook. The hookDisplayRightColumn function simply calls the hookDisplayLeftColumn function, passing its parameters and returning the result.

This is a shortcut that makes it so that the module will display the exact same in either the left or right column. Doing this also makes the code easier to maintain, since the code doesn't have to be changed in two places.

The hookDisplayHeader function is used to add JavaScript and CSS code inside the <head> tag of the website, not to be confused with the hookDisplayTop function, which adds code to the top of the website in the header.

The first line in the function adds modules/helloworldnc/js/examplenc.js to the list of JavaScript files and the second line adds modules/helloworldnc/css/helloworldnc.css to the list of CSS files with media type all. This will add the following code in the <head> tag of the website (if CCC is disabled):

<link href="http://www.yoursite.com/modules/helloworldnc/css/helloworldnc.css"
      rel="stylesheet" type="text/css" media="all" />
<script type="text/javascript"
        src="http://www.yoursite.com/modules/helloworldnc/js/helloworldnc.js">
</script>

The reason these functions are called instead of adding this code directly is so that the module's JavaScript and CSS are combined, compressed and cached along with all the other files. The module can now be added to the left and right columns.

Adding the Smarty templates

Although the module can now be added to the left and right columns, it will generate an error, since it references a Smarty template that doesn't exist. The last step to creating a basic PrestaShop module is to add the Smarty templates.

Smarty templates are files ending in .tpl that contain HTML code and embedded PHP code using a special Smarty syntax. See the official Smarty documentation for more information. Here's an example helloworldnc.tpl:

<div id="hello_world_block_left">
    <h4>{l s='Message' mod='helloworldnc'}</h4>
    <div>
        <p>Hello, {if isset($showName) AND $showName AND isset($name) AND 
           $name}{$name}{else}World{/if}</p>
    </div>
</div>

The first line creates a standard block with a unique ID so that it can be styled independently of other blocks. The second line adds the label Message at the top of the block and makes it translatable in the Back Office.

The function name l stands for language, the parameter s stands for string and the parameter mod stands for module. It is important to include the mod parameter with the name of the module for the translations to appear in the module translations.

The third line creates the content area of the block. The fourth line adds the message in the content area. When the module has its default configuration, the message will be Hello, World. If the name is changed on the configuration page, it will also change in the message.

If the module has been configured to hide the name, then only Hello will be displayed. The module is now fully functioning.

Adding CSS and JavaScript

Although the module is now fully functioning, it doesn't have any CSS or JavaScript, which is usually required to make a module useful. In the Adding the Hooks section, code was added to include JavaScript and CSS files in the module, but nothing was actually put inside them.

It's a good idea to put the CSS in a separate css directory and the JavaScript in a separate js directory to keep a clean file structure that matches the structure PrestaShop uses. Here's an example helloworldnc.css:

div#hello_world_block_left p { font-weight: bold }

In PrestaShop, it is convention to put CSS blocks containing only one line on a single line, and the last line of a CSS block doesn't have a semicolon. When there are multiple lines, it is convention to display the lines indented like this:

div#hello_world_block_left p {
    font-weight: bold;
    color: green
}

PrestaShop v1.5.x uses jQuery v1.7.2, so including it in the module is unnecessary. The only JavaScript files that need to be included are any third-party libraries required that build on jQuery. When adding inline JavaScript to the module's TPL file, be aware that Smarty will generate an error if there are any curly braces in the code. To prevent the errors, add {literal} before the JavaScript code and {/literal} after like this:

{literal}
function testFunction() { alert('Test'); }
{/literal}

When combining both JavaScript and Smarty tags, it's better to replace the JavaScript curly braces with the {ldelim} and {rdelim} tags like this:

function testFunction() {ldelim} alert('{$message}'); {rdelim}

This code will display the contents of the $message Smarty variable in a JavaScript alert box when testFunction() is called.

Creating a Back Office tab

…