Table of contents
Table of Contents | ||
---|---|---|
|
...
Creating a PrestaShop module
Creating a PrestaShop module
What is a PrestaShop module
PrestaShop's extensibility revolves around modules, which are small programs that make use of PrestaShop's functionality and changes them or add to them in order to make PrestaShop easier to use or more customized.
Technical principles behind a module
A PrestaShop module consists of:
- A root folder, named after the module, which will hold all of the module's files, and will reside in PrestaShop's
/modules
folder. - A main PHP file, named after the module, located in that root folder. This PHP file should have the same name as its root folder.
- An icon file, named
logo.gif
, representing this module. - Optional: some
.tpl
files, containing the module's theme. - Optional: language files, if the module or its theme have text to display (and therefore, that should be translatable).
- Optional: in a
/themes/modules
folder, a folder with the same name as the module, containing.tpl
and language files if necessary. This last folder is essential during modifications of existing module, so that you can adapt it without having to touch its original files. Notably, it enables you to handle the module's display in various ways, according to the current theme.
Let's see an example with PrestaShop's blockuserinfo module:
Any PrestaShop module, once installed on an online shop, can interact with one or more "hooks". Hooks enable you to "hook" your code to the current View at the time of the code parsing (i.e., when displaying the cart or the product sheet, when displaying the current stock...). Specifically, a hook is a shortcut to the various methods available from the Module object, as assigned to that hook.
Modules' operating principles
...
That is way you should always strive to stay away from core files when building a module, even though this can prove hard to do in some situations...
Module file tree
All PrestaShop modules are found in the /modules
folder, which is at the root of the PrestaShop main folder. This is true for both default modules (provided with PrestaShop) and 3rd-party modules that are subsequently installed.
...
The front-office part of the module is defined in a .tpl
file placed at the root of the module's folder. TPL files can have just about any name. It there's only one such file, it is good practice to give it the same name as the folder and main file: mymodule.tpl
.
The mymodule.php
file must start with the following test:
Code Block |
---|
if (!defined('_PS_VERSION_'))
exit;
|
This checks for the existence of a PHP constant, and if it doesn't exist, it quits. The sole purpose of this is to prevent visitors to load this file directly.
The file must also contain the module's class. PrestaShop is uses Object-Oriented Programmingprogramming, and so does do its modules.
That class must bear the same name as the module and its folder, in CamelCase: MyModule
.
Furthermore, that class must extend the Module
class, and thus inherits all methods and attributes. It can just as well extend any class derived from Module
: PaymentModule
, ModuleGridEngine
, ModuleGraph
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<?php if (!defined('_PS_VERSION_')) exit; class MyModule extends Module { public function __construct() { $this->name = 'mymodule'; $this->tab = 'Test'; $this->version = 1.0; $this->author = 'Firstname Lastname'; $this->need_instance = 0; parent::__construct(); $this->displayName = $this->l( 'My module' ); $this->description = $this->l( 'Description of my module.' ); } public function install() { if ( parent::install() == false ) return false; return true; } } ?> |
Let's examine each line from our MyModule
object...
Code Block |
---|
public function __construct()
|
Defines the class' constructor.
Code Block |
---|
$this->name = 'mymodule';
|
Assigns a 'name' attribute to our class' instance.
Code Block |
---|
$this->tab$this->tab = 'Test'; $this->version = 1.0; $this->author = 'TestPrestaShop'; |
Assigns a 'tab' attribute to our class' instanceThis section assign a handful of attributes to the class instance (this
):
- A 'name' attribute. This is an internal identifier, so make it unique, without special characters or spaces, and keep it lower-case.
- A 'tab' attribute. This is the title for the table that shall contain this module in PrestaShop's back-office modules list. You may use an existing name, such as
Products
,Blocks
orStats
, or a custom, as we did here. In this last case, a new table will be created with your title. - Version number for the module, displayed in the modules list.
- An 'author' attribute. This is displayed in the PrestaShop modules list.
Code Block |
---|
$this->version>need_instance = 1.0; |
Version number for the module, displayed in the modules listThe need_instance
flag indicates whether to load the module's class when displaying the "Modules" page in the back-office. If set at 0, the module will not be loaded, and therefore will spend less resources to generate the page module. If your modules needs to display a warning message in the "Modules" page, then you must set this attribute to 1.
Code Block |
---|
parent::__construct();
|
Calling the parent's constructor. This must be done before any use of the $this->l()
method, and after the creation of $this->name
.
Code Block |
---|
$this->displayName = $this->l( 'My module' ); |
Assigning a public name for the module, which will be displayed in the back-office's modules list.
The l()
method is part of PrestaShop translation's tools, and is explained further below.
Code Block |
---|
$this->description = $this->l('Description of my module.');
|
Assigning a public description for the module, which will be displayed in the back-office's modules list.
Code Block |
---|
public function install() { ifreturn ( parent::install() == false ) return false; return true; } |
As it isIn this first and extremely simplistic incarnation, this method is useless, since all it does is check the value returned by the Module class' install() method. Moreover, if we hadn't created that method, the superclass' method would have been called instead anyway, making the end result identical.
Nevertheless, we must mention this method, because it will be very useful once we have to perform checks and actions during the module's installation process: creating SQL tables, copying files, creation configuration variables, etc.
Likewise, the module should contain an uninstall()
method, so as to have a custom uninstallation process. This method could look as such:
Code Block |
---|
public function uninstall() { if (!parent::uninstall()) Db::getInstance()->Execute( 'DELETE FROM `' . _DB_PREFIX_ . 'block_cms` WHERE `id_block` = ' . intval( $this->id ) )'mymodule`'); parent::uninstall(); } |
To put the finishing touch to this basic module, we can add an icon, which will be displayed next to the module's name in the back-office modules list.
The icon file must respect these requirements:
- 16*16 image.
- named
logo.gif
. - place placed on the module's main folder.
You can find an excellent free set of free icons to pick from on the FamFamFam website.
Now that all basics are in place, put the module's folder in the /modules
folder of your PrestaShop test install, open PrestaShop, and in the "Modules" tab, under "Other Modules", you should find your module. Install it in order to be able to manager manage it for the rest of this guide.
...
PrestaShop automatically creates a small config.xml
file in the module's folder, which stores a few configuration information. You should NEVER edit it by hand.
On installation, PrestaShop also adds a line to the ps_module
SQL table.
Hooking a module
Displaying data, starting a process at a specific time: in order for a module to be "attached" to a location on the front-office or the back-office, you need to give it access to one of the above cited hooksmany PrestaShop hooks, described earlier in this guide.
To that effect, we are going to change your module's code, and add these lines:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
public function install() { if ( parent::install() == false OR !$this->registerHook( 'leftColumn' ) ) return false; return true; } ... public function hookLeftColumn( $params ) { global $smarty; return $this->display( __FILE__, 'mymodule.tpl' ); } public function hookRightColumn( $params ) { return $this->hookLeftColumn( $params ); } |
Let's explore these new or changed lines.
Code Block |
---|
if ( parent::install() == false OR !$this->registerHook('leftColumn') ) return false; return true; |
We changed the original line to add a second test. Now this line
This code checks:
- the boolean value returned by the
Module
class'install()
method: iftrue
, the module is installed and can be used. - the boolean value returned by
registerHook()
for theleftColumn
hook: iftrue
, the module is indeed registered to the hook it needs, and can be used.
If any of these two boolean values is false
, install()
returns false
too, and the module cannot be installed. Both values have to be true
for the module to be considered installed.
Therefore, this line now reads this way: if installation or hooking fail, we inform PrestaShop.
Code Block |
---|
public function hookLeftColumn($params)
{
global $smarty;
return $this->display(__FILE__, 'mymodule.tpl');
}
|
The hookLeftColumn()
method makes it possible for the module to hook into the theme's left column.
$smarty
is the global variable for the Smarty template system, which PrestaShop uses, and which we need to access.
The display()
method returns the content of the mymodule.tpl
template file, if it exists.
Code Block |
---|
public function hookRightColumn($params)
{
return $this->hookLeftColumn($params);
}
|
...
So, let's create the mymodule.tpl
file, and add some lines of code to it.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<!-- Block mymodule -->
<div id="mymodule_block_left" class="block">
<h4>Welcome!</h4>
<div class="block_content">
<ul>
<li><a href="{$base_dir}modules/mymodule/mymodule_page.php" title="Click this link">Click me!</a></li>
</ul>
</div>
</div>
<!-- /Block mymodule -->
|
...
Tip |
---|
You should strive to use explicit and recognizable names for your TPL files, so that you can find them quickly in the back-office – which is a must when using the translation tool. |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
Welcome to my shop!
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<?php global $smarty; include( '../../config/config.inc.php' ); include( '../../header.php' ); $smarty->display( dirname(__FILE__) . '/mymodule_page.tpl' ); include( '../../footer.php' ); ?> |
We first load the current Smarty instance. This must be done before any call to the display()
method.
...
Smarty is a PHP template engine, and is used by PrestaShop's theming system.
It parses TPL files, look looking for dynamic elements to replace by their data equivalents, then displays the generated result. Those dynamic elements are indicated with curly brackets ?. The coder : { ... }. The programmer may create new variables and use them in TPL files.
For instance, in our mymodule_page.php
, we can create such a a variable:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<?php global $smarty; include( '../../config/config.inc.php' ); include( '../../header.php' ); $mymodule = new MyModule(); $message = $mymodule->l( 'Welcome to my shop!' ); $smarty->assign( 'messageSmarty', $message ); // creation of our variable $smarty->display( dirname(__FILE__) . '/mymodule_page.tpl' ); include( '../../footer.php' ); ?> |
From there on, we can ask Smarty to display the content of this variable in our TPL file.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
{$messageSmarty}
|
PrestaShop includes a number of variables. For instance, {$HOOK_LEFT_COLUMN} will be replaced with the content for the left column, meaning the content from all the modules that have been attached to the left column's hook.
All Smarty variables are global. You should therefore pay attention not to name your own variable with the name of an existing Smarty variable, in order to avoid overwriting it. It is good practice to avoid overly simple names, such as products
, and to prefix it with your module's name, or even your own name, such as: {$mark_mymodule_product}.
Here is a list of Smarty variables that are common to all pages:
...
If you need to have all of the current page's Smarty variables displayed, add the following function:
Code Block |
---|
{debug}
|
Comments are based on asterisk:
Code Block |
---|
{* This string is commented out *} {* This string is too! *} |
Unlike with a HTML commentcomments, commented-out Smarty code is not present in the final output file.
...
Strings in PHP files will need to be displayed through the l()
method, from the Module.php
abstract class.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
... $this->displayName = $this->l( 'My module' ); $this->description = $this->l( 'Description of my module.' ); ... |
Strings in TPL files will need to be turned into dynamic content, which Smarty will replace by the translation for the chosen language. In our sample module, this file:
Code Block | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||
<li>
<a href="{$base_dir}modules/mymodule/mymodule_page.php" title="Click this link">Click me!</a>
</li>
|
...becomes:
Code Block | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||
<li>
<a href="{$base_dir}modules/mymodule/mymodule_page.php" title="{l s='Click this link' mod='mymodule'}">{l s='Click me!' mod='mymodule'}</a>
</li>
|
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<h4>Welcome!</h4>
...
Click me!
|
...becomes:
Code Block | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||
<h4>{l s='Welcome!' mod='mymodule'}</h4>
...
{l s='Click me!' mod='mymodule'}
|
...
The translations are saved in a new file, fr.php
(or languageCode.php
, which is generated by PrestaShop and looks like this:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<?php
global $_MODULE;
$_MODULE = array();
$_MODULE['<{mymodule}prestashop>mymodule_2ddddc2a736e4128ce1cdfd22b041e7f'] = 'Mon module';
$_MODULE['<{mymodule}prestashop>mymodule_d6968577f69f08c93c209bd8b6b3d4d5'] = 'Description de mon module';
$_MODULE['<{mymodule}prestashop>mymodule_c66b10fbf9cb6526d0f7d7a602a09b75'] = 'Cliquez sur ce lien';
$_MODULE['<{mymodule}prestashop>mymodule_f42c5e677c97b2167e7e6b1e0028ec6d'] = 'Cliquez-moi \!';
$_MODULE['<{mymodule}prestashop>mymodule_page_c0d7cffa0105851272f83d5c1fe63a1c'] = 'Bienvenue dans ma boutique \!';
|
...
In this section you will learn how to give your module its own tab or sub-tab, in a matter of minutes.
Follw Follow these steps:
- Add a new table to your PrestaShop database, named
ps_test
. Give it two fields:id_test
(INT 11)
test
(VARCHAR 32)
- Create a blank file named
Test.php
in PrestaShop's/classes
folder. - Add the following lines to that file:
Code Block | |||||
---|---|---|---|---|---|
|
| ||||
<?php class Test extends ObjectModel { /** @var string Name */ public $test; protected $fieldsRequired = array('test'); protected $fieldsSize = array('test' => 64); protected $fieldsValidate = array('test' => 'isGenericName'); protected $table = 'test'; protected $identifier = 'id_test'; public function getFields() \ { parent::validateFields(); $fields['test'] = pSQL($this->test); return $fields; } } ?> |
- Create a blank file named
AdminTest.php
in PrestaShop's/admin/tabs
. - Add the following lines to that file:
Code Block | |||||||
---|---|---|---|---|---|---|---|
|
| ||||||
<?php include_once( PS_ADMIN_DIR.'/../classes/AdminTab.php'); class AdminTest extends AdminTab { public function \__construct() { $this->table = 'test'; $this->className = 'Test'; $this->lang = false; $this->edit = true; $this->delete = true; $this->fieldsDisplay = array( 'id_test' => array( 'title' => $this->l('ID'), 'align' => 'center', 'width' => 25), 'test' => array( 'title' => $this->l('Name'), 'width' => 200) ); $this->identifier = 'id_test'; parent::__construct(); } public function displayForm() { global $currentIndex; $defaultLanguage = intval(Configuration::get('PS_LANG_DEFAULT')); $languages = Language::getLanguages(); $obj = $this->loadObject(true); echo ' <script type="text/javascript"> id_language = Number('.$defaultLanguage.'); </script>'; echo ' <form action="' . $currentIndex . '&submitAdd' . $this->table . '=1&token=' . $this->token . '" method="post" class="width3"> ' . ($obj->id ? '<input type="hidden" name="id_' . $this->table . '" value="' . $obj->id . '" />' : '').' <fieldset><legend><img src="../img/admin/profiles.png" />' . $this->l('Profiles') . '</legend> <label>'.$this->l('Name:').' </label> <div class="margin-form">'; foreach ( $languages as $language ) echo ' <div id="name_' . $language['id_lang'|'id_lang'] . '" style="display: ' . ($language['id_lang'|'id_lang'] == $defaultLanguage ? 'block' : 'none') . '; float: left;"> <input size="33" type="text" name="name_' . $language['id_lang'|'id_lang'] . '" value="' . htmlentities( $this->getFieldValue( $obj, 'name', intval( $language['id_lang'|'id_lang'] ) ), ENT_COMPAT, 'UTF-8' ) . '" /><sup>*</sup> </div>'; $this->displayFlags( $languages, $defaultLanguage, 'name', 'name' ); echo ' <div class="clear"></div> </div> <div class="margin-form"> <input type="submit" value="'.$this->l(' Save ').'" name="submitAdd'.$this->table.'" class="button" /> </div> <div class="small"><sup>*</sup> '.$this->l('Required field').'</div> </fieldset> </form> '; } } ?> |
Put the files online, then create the tab by going to the "Employee" tab, then its "Tabs" sub-tab. Click the "Add new" button, and fill-in the fields with the class' name, "AdminTest". Do not confuse "class" with "modules"! Choose an icon (like one from the FamFamFam pack), choose where the tab should go, and save. You're set! Now start customizing it to your needs!
A list of PrestaShop hooks
Here's a recap of PrestaShop's module architecture:
When one of the site's page is loaded, the PrestaShop engine check which are the modules to call for each of the hooks that make up the page.
Here is a list of 53 hooks, available in PrestaShop.
Front-office
Homepage and general website items
Hook name | File location | Visible | Description |
---|---|---|---|
header | header.php | No | Called between the HEAD tags. Ideal location for adding JavaScript and CSS files. |
top | header.php | Yes | Called in the page's header. |
leftColumn | header.php | Yes | Called when loading the left column. |
rightColumn | footer.php | Yes | Called when loading the right column. |
footer | footer.php | Yes | Called in the page's footer. |
home | index.php | Yes | Called at the center of the homepage. |
Product sheet
Hook name | File location | Visible | Description |
---|---|---|---|
extraLeft | product.php | Yes | Called right before the "Print" link, under the picture. |
extraRight | product.php | Yes | Called right after the block for the "Add to Cart" button. |
productActions | product.php | Yes | Called inside the block for the "Add to Cart" button, right after that button. |
productOutOfStock | product.php | Yes | Called inside the block for the "Add to Cart" button, right after the "Availability" information. |
productfooter | product.php | Yes | Called right before the tabs. |
productTab | product.php | Yes | Called in tabs list, such as "More info", "Data sheet", "Accessories"... Ideal location for one more tab, the content of which is handled by the |
productTabContent | product.php | Yes | Called when a tab is clicked. Ideal location for the content of a tab that has been defined using the |
Cart
Hook name | File location | Visible | Description |
---|---|---|---|
cart | Class: Cart.php | No | Called right after a cart creation or update. |
shoppingCart | order.php | Yes | Called right below the cart items table. |
shoppingCartExtra | order.php | Yes | Called after the cart's table of items, right above the navigation buttons. |
createAccountTop | authentication.php | Yes | Called within the client account creation form, right above the the "Your personal information" block. |
createAccountForm | authentication.php | Yes | Called within the client account creation form, right before the "Register" button. |
createAccount | authentication.php | No | Called right after the client account creation. |
customerAccount | my-account.php | Yes | Called on the client account homepage, after the list of available links. Ideal location to add a link to this list. |
myAccountBlock | Module: blockmyaccount.php | Yes | Called within the "My account" block, in the left column, below the list of available links. Ideal location to add a link to this list. |
authentication | authentication.php | No | Called right after the client identification, only if the authentication is valid (e-mail address and password are both OK). |
Search
Hook name | File location | Visible | Description |
---|---|---|---|
search | Class: Search.php | No | Called after a search is performed. Ideal location to parse and/or handle the search query and results. |
Carrier choice
Hook name | File location | Visible | Description |
---|---|---|---|
extraCarrier | order.php | Yes | Called after the list of available carriers, during the order process. Ideal location to add a carrier, as added by a module. |
Payment
Hook name | File location | Visible | Description |
---|---|---|---|
payment | order.php | Yes | Called when needing to build a list of the available payment solutions, during the order process. Ideal location to enable the choice of a payment module that you have developed. |
paymentReturn | order-confirmation.php | Yes | Called when the user is sent back to the store after having paid on the 3rd-party website. Ideal location to display a confirmation message or to give some details on the payment. |
orderConfirmation | order-confirmation.php | Yes | A duplicate of |
backBeforePayment | order.php | No | Called when displaying the list of available payment solutions. Ideal location to redirect the user instead of displaying said list (i.e., 1-click PayPal checkout).. |
Merchandise Returns
Hook name | File location | Visible | Description |
---|---|---|---|
orderReturn | order-follow.php | No | Called when the customer request to send his merchandise back to the store, and if now error occurs. |
PDFInvoice | Class: PDF.php | Yes | Called when displaying the invoice in PDF format. Ideal location to display dynamic or static content within the invoice. |
Back-office
General
Hook name | File location | Visible | Description |
---|---|---|---|
backOfficeTop | header.inc.php | Yes | Called within the header, above the tabs. |
backOfficeHeader | header.inc.php | No | Called between the HEAD tags. Ideal location for adding JavaScript and CSS files. |
backOfficeFooter | footer.inc.php | Yes | Called within the page footer, above the "Power By PrestaShop" line. |
backOfficeHome | index.php | Yes | Called at the center of the homepage. |
Orders and order details
Hook name | File location | Visible | Description |
---|---|---|---|
newOrder | Class: PaymentModule.php | No | Called during the new order creation process, right after it has been created. |
paymentConfirm | Class: Hook.php | No | Called when an order's status becomes "Payment accepted". |
updateOrderStatus | Class: OrderHistory.php | No | Called when an order's status is changed, right before it is actually changed. |
postUpdateOrderStatus | Class: OrderHistory.php | No | Called when an order's status is changed, right after it is actually changed. |
cancelProduct | AdminOrders.php | No | Called when an item is deleted from an order, right after the deletion. |
invoice | AdminOrders.php | Yes | Called when the order's details are displayed, above the Client Information block. |
adminOrder | AdminOrders.php | Yes | Called when the order's details are displayed, below the Client Information block. |
orderSlip | AdminOrders.php | No | Called during the creation of a credit note, right after it has been created. |
Products
Hook name | File location | Visible | Description |
---|---|---|---|
addproduct | AdminProducts.php | No | Called when a product is created or duplicated, right after said creation/duplication. |
updateproduct | AdminProducts.php | No | Called when a product is update with a new picture, right after said update. |
deleteproduct | Class: Product.php | No | Called when a product is deleted, right before said deletion.. |
updateQuantity | Class: PaymentModule.php | No | Called during an the validation of an order, the status of which being something other than "canceled" or "Payment error", for each of the order's items. |
updateProductAttribute | Class: Product.php | No | Called when a product declination is updated, right after said update. |
watermark | AdminProducts.php | No | Called when an image is added to an product, right after said addition. |
Statistics
Hook name | File location | Visible | Description |
---|---|---|---|
GraphEngine | Class: ModuleGraph.php | Yes | Called when a stats graph is displayed. |
GridEngine | Module: GridEngine.php | Yes | Called when the grid of stats is displayed. |
AdminStatsModules | AdminStatsTab.php | Yes | Called when the list of stats modules is displayed. |
Clients
Hook name | File location | Visible | Description |
---|---|---|---|
adminCustomers | AdminCustomers.php | Yes | Called when a client's details are displayed, right after the list of the clients groups the current client belongs to. |
Carriers
Hook name | File location | Visible | Description |
---|---|---|---|
updateCarrier | AdminCarriers.php | No | Called during a carrier's update, right after said update. |
Troubleshooting
If your module does not work as expected, here are a few ways to find help.
...
Some forums keep certain threads pinned on top of all threads; they contain some useful information, so be sure to read them through.
Our bug-tracker
if If it turns out your issue stems from a PrestaShop bug rather than your code, please do submit the issue in the PrestaShop bug-tracker: http://forge.prestashop.com/ (you will need to register). This enables you to discuss the issue directly with the PrestaShop developers.
...