Creating new pages for your Prestashop store

Prestashop has lots of pages to display specific stuff, but it may not be enough. What if we want to show all of our products to the customer, for example? We’d need a brand new page with its controller. Let’s see how easy it’s become to add new pages to Prestashop since 1.5.

Download Project Files

Introduction

You might have noticed a huge difference in the look of your URLs (with no rewriting enabled), if you were used to prestashop 1.4. For example, sitename.com/prices-drop.php, used to display special-priced products, is now sitename.com/index.php?controller=prices-drop.

Basically, all the pages’ differentiation structure is delegated to the index page, which runs a “dispatcher”, that grants control to the appropriate controller based on the url. This is a really powerful feature, that allows all modules to create a new page, just by writing 2 more query parameters. Therefore, sitename.com/index.php?fc=module&module=mymodule&controller=allproducts will trigger the “allproducts” controller of the “mymodule” module. This, of course, for the front office. The back office structure is similar, but I won’t dive into that since the aim of this article is to teach you how to add new pages to the store, not to the admin panel. I’ll probably write something about that sometime soon.

The old-fashioned way to create new pages is not totally gone, anyway. If you want, you can create a new controller, then a new class to hold it, place them inside the root’s respective folders, and be happy with it. However, this way it is much, much more consistent.

In the example we will create a page to display all the store’s products (Uncategorized and with pagination; Not sure how this can be useful for purposes other than demonstrating the process of creating new pages though!). Let’s get started!

Step 1 – Creating the correct folder structure

First of all, grab the folder provided in the project files, called “testmodule” and place it inside your modules folder. Go ahead and install it. It won’t do anything, for the time being, but the module has to be installed in order for the new front page to be visibile.

Inside this module’s folder, create a new, empty folder, named controllers. Inside this, another one called front. This is where our file will be placed. Finally, create a new, blank PHP file here, called allproducts.php

 

Step 2 – Adding the new page’s controller

Open up the newly created PHP file in your favorite code editor. We need to use a specific syntax to create the correct controller at this point. If you already had a look a the official Prestashop documentation about this, and consequently tried to shoot at your head, I won’t blame you.

It’s just telling you what to do, not why you’re doing it. So, basically, here is the structure for our controller class:

moduleName*filename*ModuleFrontcontroller extends ModuleFrontController. This, of course, without asterisks, which are there only to indicate which parts can be changed. Therefore, for a module named “crazy”, and a controller file named “race.php”, that class declaration would be crazyRaceModuleFrontController extends ModuleFrontController. Damn camel-cases. They should use underscores instead *whew!*.

It’s not that hard after having broken it down. So, let’s write the class declaration for our controller, inside that allproducts.php file:

	Class testmoduleAllproductsModuleFrontController extends ModuleFrontController
	{

	}

Easy enough, now that we know the structure; remember, it’s modulename*filename*ModuleFrontcontroller extends ModuleFrontController. If you’re wondering “Where do i put uppercase letters?” the answer is: “Where you think they can help you read the name”: these classes are all processed in a case-insensitive manner.

Now that the class is there, let’s go to index.php?fc=module&module=testmodule&controller=allproducts. You should have something like this:

Prestashop blank controller page

Blank page to work with

We have an empty page to work with now. We just have to fill in the center column, which is empty for the time being.

Some usefult methods

Let’s begin writing our controller. First, we want to have a look at the functions we can access: the ones you’ll probably use most are initContent() and setMedia(). Another useful though not so essential method is init(), which runs as soon as the class is instanciated, right after the constructor.

This last one is probably useless in most cases, but can have its benefits. We can, for instance, change the page name (and subsequently the id that is assigned to the body in that page), and decide if we need or not the left/right column. But let’s start off with a blank method:

	public function init()
	{
		parent::init();
	}

The only thing I’m doing here is calling the paren’t init(), otherwise you won’t get anything in the page at all. Calling the paren’t function with the same name is always recommended (if not necessary) when extending Prestashop, unless you really want to override that method. Let’s refresh the page, and inspect it with some debugging tools (I’ll use Chrome Dev Tools): the body’s id is module-testmodule-allproducts. Again, this is a pattern: module-*modulename*-*controllername*. This is also the name for this page. It’s pretty ugly, but we have the ability to change it if really needed. Let’s edit our init() method as follows:

	public function init()
	{
		$this->page_name = 'allproducts'; // page_name and body id
		parent::init();
	}	

Refresh the page. You’ll now notice the body id has changed, reflecting our variable assignment. Another thing we can do is hide either of the columns, or both, by assigning the following variables:

	$this->display_column_left = false;
	$this->display_column_right = false;

If you add these to the init method, both of the columns will be hidden. These, and the page name assignment, must always be placed before calling the parent’s init() method, otherwise they’ll be useless. Not that this procedure, as of the latest version (1.5.3.1), creates a huge problem when developing themes: some modules hide either column (most likely, the left one) on their own, forcing your template to be as they want. There currently is no way to avoid it, therefore use this carefully if you plan to sell modules with custom pages! Since we are dealing with an example, I’ll hide the left column:

	public function init()
	{
		$this->page_name = 'allproducts'; // page_name and body id
		$this->display_column_left = false; // hides left column
		parent::init();
	}	

 

initContent()

This is the core function for any front page controller. Let’s try it out, adding the method after init():

	public function initContent()
	{
		parent::initContent();
		echo'hey there';
	}	

We need, again, to call the parent’s method with the same name. For this one, it should be done at the very beginning. Refresh the page, and if everything is done correctly, “hey there” should appear in the top left corner of the page.

Step 3 – Adding the template

So far we only dealt with php, but it’s time to display some content in the page (otherwise, it’s just useless, isn’t it?). In the module’s folder, create the following folder structure: views/templates/front/. Inside front/, create a file named allproducts.tpl. This procedure will sound logical to you, if you’re familiar with the MVC structure. We are currently using the module’s core file (testmodule.php) as a Model (even though we’ll be using functions from other classes in the example), the allproducts.php file as the Controller, and this last file as a View.

Now that we have our view, we need to set it as the template file. To do it, add the following (and remove the ‘hi there’ placeholder) inside initContent():

	$this->setTemplate('allproducts.tpl');

We can simply use the template file’s name, since by default controllers look for the folder structure we created. Let’s start filling our template now, and since we’re still lacking real content (products), simply add some title tag to be sure the file is being loaded:

	

{l s='All products' mod='testmodule'}

 

Step 4 – Filling our page with products

For the tme being, we will limit ourselves to using Prestashop’s core functions to display products (we’ll enhance this later on). let’s grab them in the initContent function of our controller:

	public function initContent()
	{
		parent::initContent();

		$products_partial = Product::getProducts($this->context->language->id, 0, 5, 'name', 'asc');
		$products = Product::getProductsProperties($this->context->language->id, $products_partial);

		$this->context->smarty->assign(array(
			'products' => $products,
			'homeSize' => Image::getSize('home_default')
		));
		$this->setTemplate('allproducts.tpl');
	}

Explanation: First, we retrieve all products by calling the product class’s getProducts, passing, in the order, these parameters: current language id, skip #, limit #, order by, order way. I’m just using some arbitrary pagination values, since we’ve not created any pagination function so far. If you simply want to display ALL of your products at once (absolutely not recommended if they’re more than 50, and in any case depends on your server), use 0 instead of 5. We then need to retrieve all relevant data to display a decent amount of product properties, therefore we call getProductsProperties which accepts the language id and the previous partial data.
Finally, we are assigning them to the ‘products’ variable for the template. We also assign an image type since we’ll be displaying images.

Now that our products have been retrieved, we must display them; add the following code in the template file (it’s copied and pasted from the original product-list.tpl file):

	
{if isset($products)}
    <!-- Products list -->
    <ul id="product_list" class="clear">
    {foreach from=$products item=product name=products}
        <li class="ajax_block_product {if $smarty.foreach.products.first}first_item{elseif $smarty.foreach.products.last}last_item{/if} {if $smarty.foreach.products.index % 2}alternate_item{else}item{/if} clearfix">
            <div class="left_block">
                {if isset($comparator_max_item) && $comparator_max_item}
                    <p class="compare">
                        <input type="checkbox" class="comparator" id="comparator_item_{$product.id_product}" value="comparator_item_{$product.id_product}" {if isset($compareProducts) && in_array($product.id_product, $compareProducts)}checked="checked"{/if} /> 
                        <label for="comparator_item_{$product.id_product}">{l s='Select to compare'}</label>
                    </p>
                {/if}
            </div>
            <div class="center_block">
                <a href="{$product.link|escape:'htmlall':'UTF-8'}" class="product_img_link" title="{$product.name|escape:'htmlall':'UTF-8'}">
                    <img src="{$link->getImageLink($product.link_rewrite, $product.id_image, 'home_default')}" {if isset($homeSize)} width="{$homeSize.width}" height="{$homeSize.height}"{/if} />
                    {if isset($product.new) && $product.new == 1}<span class="new">{l s='New'}</span>{/if}
                </a>
                <h3><a href="{$product.link|escape:'htmlall':'UTF-8'}" title="{$product.name|escape:'htmlall':'UTF-8'}">{$product.name|escape:'htmlall':'UTF-8'|truncate:35:'...'}</a></h3>
                <p class="product_desc"><a href="{$product.link|escape:'htmlall':'UTF-8'}" title="{$product.description_short|strip_tags:'UTF-8'|truncate:360:'...'}" >{$product.description_short|strip_tags:'UTF-8'|truncate:360:'...'}</a></p>
            </div>
            <div class="right_block">
                {if isset($product.on_sale) && $product.on_sale && isset($product.show_price) && $product.show_price && !$PS_CATALOG_MODE}<span class="on_sale">{l s='On sale!'}</span>
                {elseif isset($product.reduction) && $product.reduction && isset($product.show_price) && $product.show_price && !$PS_CATALOG_MODE}<span class="discount">{l s='Reduced price!'}</span>{/if}
                {if (!$PS_CATALOG_MODE AND ((isset($product.show_price) && $product.show_price) || (isset($product.available_for_order) && $product.available_for_order)))}
                <div class="content_price">
                    {if isset($product.show_price) && $product.show_price && !isset($restricted_country_mode)}<span class="price" style="display: inline;">{if !$priceDisplay}{convertPrice price=$product.price}{else}{convertPrice price=$product.price_tax_exc}{/if}</span><br />{/if}
                    {if isset($product.available_for_order) && $product.available_for_order && !isset($restricted_country_mode)}<span class="availability">{if ($product.allow_oosp || $product.quantity > 0)}{l s='Available'}{elseif (isset($product.quantity_all_versions) && $product.quantity_all_versions > 0)}{l s='Product available with different options'}{else}{l s='Out of stock'}{/if}</span>{/if}
                </div>
                {if isset($product.online_only) && $product.online_only}<span class="online_only">{l s='Online only!'}</span>{/if}
                {/if}
                {if ($product.id_product_attribute == 0 || (isset($add_prod_display) && ($add_prod_display == 1))) && $product.available_for_order && !isset($restricted_country_mode) && $product.minimal_quantity <= 1 && $product.customizable != 2 && !$PS_CATALOG_MODE}
                    {if ($product.allow_oosp || $product.quantity > 0)}
                        {if isset($static_token)}
                            <a class="button ajax_add_to_cart_button exclusive" rel="ajax_id_product_{$product.id_product|intval}" href="{$link->getPageLink('cart',false, NULL, "add&amp;id_product={$product.id_product|intval}&amp;token={$static_token}", false)}" title="{l s='Add to cart'}"><span></span>{l s='Add to cart'}</a>
                        {else}
                            <a class="button ajax_add_to_cart_button exclusive" rel="ajax_id_product_{$product.id_product|intval}" href="{$link->getPageLink('cart',false, NULL, "add&amp;id_product={$product.id_product|intval}", false)}" title="{l s='Add to cart'}"><span></span>{l s='Add to cart'}</a>
                        {/if}                       
                    {else}
                        <span class="exclusive"><span></span>{l s='Add to cart'}</span><br />
                    {/if}
                {/if}
            </div>
        </li>
    {/foreach}
    </ul>
    <!-- /Products list -->
{/if}

Notice: i had to remove the “alt” tag for the image, since it was not retrieved in the getProducts method and was causing troubles. Also, images are not available at this stage for the same reason.

Refresh the front page, you should see something like this:

Base products list

Base products list

Step 5 – Adding some style

Okay, the page basically looks as the products list. But there are some changes we need to apply. The central block must be larger, and let’s say we also want to add a faded gray background to each product. How to? well, usually, we would have used the header hook to add the css from the module, and maybe that’s an option, using the page_name variable and only triggering addCSS if that’s the current page. But there’s a nicer and cleaner way: we will make use of setMedia(). Add this method in the controller (a logical position is between init and initContent, but can be anywhere):

	public function setMedia()
	{
		parent::setMedia();
		$this->addCSS(__PS_BASE_URI__.'modules/'.$this->module->name.'/css/'.$this->module->name.'.css');
		
	}	

Explanation: As always, we call the parent’s method, and then we add the css that should be located in the module’s folder /css/testmodule.css. You can see I introduced a new variable here: $this->module. This object represents the module’s core file, which is testmodule.php. I can access all properties and methods of this object, therefore, since my stylesheet has the same name of the module, I use $this->module->name to reference it.

Now that testmodule.css has been added to this page (and this only!), fill it with the following code:


#products_list  .ajax_block_product {
    background:#fafafa;
}

#products_list .ajax_block_product .center_block {
    width: 550px !important;
}	
	

There is nothing special to it, since its only purpose is to demonstrate the process. Of course, the same would be for javascript files, which would be added using $this->context->controller->addJS in that very same setMedia() method.

Step 6 – Adding pagination

First, in order to add pagination, we need to know exactly how many products we have. Thus, we must count them with a custom function. In the testmodule.php file, let’s create a custom method:

	public function countAllProducts()
	{
		return Db::getInstance()->getValue('SELECT COUNT(*) from ps_product WHERE active = 1');
	}	

Then, in our controller, add this right before $products_partials:

	$products_count = $this->module->countAllProducts();

Finally, let’s call the pagination function (comes from FrontController) which assigns all the variables we need to the page). This must go after $products_count, but before getting any product data.

	$this->pagination($products_count);

Remember that we initially hard-coded the page number and number of products? It’s time to fix it. Amend the getProducts function call like this

	$products_partial = Product::getProducts($this->context->language->id, ((int)$this->p - 1) * (int)$this->n, $this->n, 'name', 'asc');	

We’re basically referencing two variables that come from the FrontController. First, for the “skip x products” parameter, we do some math to retrieve, thanks to the page number ($this->p) how many products we must skip for this page; then, we pass the number of products per page. If the first expression looks confusing to you, here is a little example: We are on page 1, one minus one is zero, and zero times the number of products per page is zero. Thus, in the first page we don’t want to skip any product (we get the first 5, for example). If page number is 2, two minus one is one, times 5 (products per page in the example) is 5. So, in page 2 we don’t want to display the first 5 products, which were shown in page one. In any case, here is how initContent should look

	public function initContent()
	{
		parent::initContent();

		$products_count = $this->module->countAllProducts();
		$this->pagination($products_count); // needs to be here, so that page number and products per page are assigned to "p" and "n"
		
		$products_partial = Product::getProducts($this->context->language->id, ((int)$this->p - 1) * (int)$this->n, $this->n, 'name', 'asc');
		$products = Product::getProductsProperties($this->context->language->id, $products_partial);


		$this->context->smarty->assign(array(
			'products' => $products,
			'homeSize' => Image::getSize('home_default')
		));
		$this->setTemplate('allproducts.tpl');
	}	

Time to test this out! Add “&p=2″ to the current url, and you’ll see you get the second page, and so on for all the valid numbers. Not that this is useful actually, we need to implement pagination visually. We’ll use the very same category pages pagination, therefore it will be enough to add the following code at the end of the template file, before the closing {/if}

	{include file="$tpl_dir./pagination.tpl"}

Step 7 – Finishing touches

We are basically done…but wait! We’re still missing product images. Let’s add a bit of code to the controller, right before assigning the products to the template:

		/* Retrieving product images */

		foreach ($products as $key => $product) {
			foreach ($products as $key => $product) {
				$products[$key]['id_image'] = Product::getCover($product['id_product'])['id_image'];
			}
		}	

Explanation: Nothing fancy: we iterate over all products, and for each of them, we get the cover image. Since the result of Product::getCover is an array type, we also need to specify that we want to grab the id_image key at the end.

Conclusion

And that’s it. We now have a brand new page. Of course, nobody would probably need a “show me all products” section, but the purpose of this tutorial was to showcase the posibilities when trying to create new pages with modules. This was only one way, and can be greatly enhanced. For example, if you have a custom type of data (Say, customers testimonials, or blog posts) you might want to create a separate Model (class) that takes care of it, instead of relying on the module’s methods, which is perfectly fine in any case.

You like the tuts and want to say "thank you"? Well, you can always feel free to donate:

  • Tah Teche

    @NemoPS:disqus Its funny you think nobody wants such a module. This is exactly what I wanted. :-)

  • Tah Teche

    Thanks @DuoCreative . I realized there was a problem with this code snippet but did not know how to fix it.
    Thanks a lot.

  • Mihai

    Hello.In prestashop 1.6 we can change the link of module? I don’t like this link index.php?fc=module&module=testmodule&controller=allproducts

    Thanks for tutorial.

    • NemoPS

      You can simply enable Friendly URLs in the SEO&URLs preferences in the back office :)

  • Angela

    Hi
    Also me I would to create a “new products” page for each category.
    How I can do it?
    Thanks
    Angela

  • Adrien K.

    Hi,

    Thank you! This tutorial really helps me.

    I have a little question : How can I set the meta_title, meta_description and meta_keywords for this page?

    I tried something like that in the init function but it doesn’t work :


    $this->meta_title = 'E-parquets Boulogne Billancourt';

    Thanks for your help.

  • Lakshmi Bade

    Hi,

    I followed your tutorial. It’s Really helpful. But i want the url is seo friendly.

    In your example the url should be like `/allproducts`

    Is that possible?

    Thanks,
    Laks.

    • NemoPS

      With the page created from a module, as far as I know, no. You can do it if you create a new base controller on its own, from the SEO and urls page.

      • kundan kumar

        I wnt to add 1-2 customize pages with coding in prestashop 1.6.0.11, please can help me in this, can you provide me complete coding for extra page .
        And I want following things in the extra pages.
        1) I want a full width banner.
        2) I want to add offers images in 4-6 groups with text bar above them to differentiate the offers, like this i want to add 16-25 images in one page. And I want to change the images from the back-office9admin-panel)
        3) With common footer.
        Please help me

      • Aselle

        Hi,

        Thank you so much for this tutorial, at least a clear way to follow! :)

        Three years passed after this reply you’ve maid to Lakshmi. Has something changed since that? Is it now possible to make a friendly URL for a custom page with custom module?

        Also, are you still available for custom module developpement? It would help us a lot!

        Thanks in advance for your reply!
        Aselle

  • Lakshmi Bade

    Great tutorial :)

  • http://www.felixsolis.fr ELM

    Thank you for the great tuto.
    How would you do for displaying not all products, but only products with a common feature?
    Thanks again!

  • greeshma

    Hi,

    Great tut. I followed it and the module works great in my local environment. But I am unable to access the url when it comes to my live site.

    Pls advice me what the issue could be.

  • http://thevietnamwar.info Tim

    Thanks a lot for the tutorial. I have a question though.

    I want to create a “new products” page for each category.

    Though I’m able to create that new controller (page) based on your tutorial, I actually want the page’s breadcrumb to be under each category too.

    For example: Home > Men > New Products

    Currently it is: Home > New Products (for all category)

    Could you give me some hints how to achieve that?

    Thanks a lot!

  • http://setik.it daniele

    Hello!
    i have a question on the last foreach for the image.

    i paste the code in the controller but i don’t see the image.
    my code:

    public function initContent() {
    parent::initContent();

    $products_count = $this->module->countAllProducts();
    $this->pagination($products_count);

    $products_partial = Product::getProducts($this->context->language->id, ((int) $this->p - 1) * (int) $this->n, $this->n, 'name', 'asc');
    $products = Product::getProductsProperties($this->context->language->id, $products_partial);

    $this->context->smarty->assign(array(
    'products' => $products,
    'homeSize' => Image::getSize('home_default')
    ));

    $this->setTemplate('allproducts.tpl');
    }

    where is my error??

    can you help me of the rewrite url??
    how i do it??

    thanks a lot for the tuts!!
    this is very interesting !!

    by daniele!

    • http://setik.it daniele

      resolve.

      but i don’t change the name of module and i not say why..

  • Trusted

    Hi, excellent tut,

    can you tel me how you worked out what functions to use?

    Like how did you know that

    Product::getProducts($this->context->language->id, ((int)$this->p – 1) * (int)$this->n, $this->n, ‘name’, ‘asc’);

    would get you the products that you want?

    Is there a tut on this or is it in the docs or is it by studying the core?

    Thanks a lot

    • Rimesta

      the function that he used is like that :

      public function getProducts($id_lang, $p, $n, $order_by = null, $order_way = null, $get_total = false, $active = true, $random = false, $random_number_products = 1, $check_access = true, Context $context = null)

  • Michael

    Thanks a lot for the great guide. Works fine for me with version 1.5.x .
    My next challenge is to get the product list out of an external soap interface. Do you have some experience with this kind of data for prestashop ?

    Thanks in advance. Kind Regards

    Michael

  • http://www.scriptarticle.com Mahesh

    I like it.. very useful and easy.. Thanks Admin

  • http://www.duocreative.co.uk DuoCreative

    Hi there.

    Really liked the tutorial very easy to follow and gave me a head start into understanding the inner workings of Prestashop.

    However there is one correction I’d like to make to it, I image others will run into this:

    Step 7 – Finishing touches, the code is wrong it should be:

    foreach ($products as $key => $product) {

    $cover = Product::getCover($product['id_product']);

    $products[$key]["id_image"] = $cover["id_image"];

    }

    Just in case anyone gets stuck!

    Thanks for a great tutorial :)

    • Rimesta

      Hello,
      Thanks for all for sharing this tuto,
      The module works fine but images are not loaded.
      I used both methods to (Nemo & DuoCreative Method) to get images but i got nothing..

      My Prestashop version is : 1.5.4.1

  • http://www.mimo81.com mimo81

    Hi, the module doesn’t work for my PS 1.4.8.2,, I want to create a new controller, what should I do?

    • http://www.duocreative.co.uk DuoCreative

      Well the tutorial is for Prestashop 1.5 … So why would it work?

  • Fire

    You said, you’d create a tutorial to explain how the admin part is workin’. It would be really useful :)

    Could you please let me know, if you plan to work on such a thing. I really need it right now :)

    • http://nemops.com Nemo

      Hi,
      Yeah, well, I’ve been drowning in commitments the last two months, hopefully I’ll be able to do it in june! :D

      • Fire

        Ok I’ll give it a sight, but I need to find something for now since I’m building a startup based on Prestashop.

        but thanks what you do is awesome !

  • Fire

    Thanks a lot for this simple explanation. I don’t know why but comments were making my controler bug so I just removed them and it’s perfectly workin’ !

    This tutorial is a rmust have if you want to start coding module with Prestashop :)

    Thanks again !

  • Maury Markowitz

    Geez, your articles are reading my mind! I was thinking about just this. But now I’m thinking that it’s not the right solution. I think maybe Presta does what I want, and I just haven’t seen an example yet. So if you don’t mind me asking for some advice…

    Most of our products fall into “families” that link together a bunch of products from a single company. For instance, one of ours sells a device called a “smart combiner box” that comes in three models. Aside from the size, they’re all the same thing. They also sell parts that go inside those boxes, mostly a bunch of different fuses.

    Meanwhile, other companies also sell similar “smart combiner box”es and parts, but they are definitely not the same family. And most of the parts can’t be swapped from one to the other (but of course, not always).

    I’m looking for advice on how to best handle this in presta. At first I was going to add a Feature (or even just a tag, right?) and make a new page that collected all the products with that feature (tag).

    But now I’m wondering if there isn’t some way to do this using the parts that Presta already has. It’s sort of like associations, I think. But I’m not at all clear how these work. The Presa docs are terrible, does anyone have a really in-depth guide to power using these?

    It also seems like I could do some of this with categories, one for each family. Maybe these are attached to a different root so they don’t appear in the normal navigation? Then I would link to them directly (can you link to a disabled cat?).

    But ideally the entire family would appear in a single page, like this hand-crafted example. I’d still want products to appear on their own, because people search for them, but maybe I’d rel=canonical back to the family page?

    Does any of this make sense? Have any of you out there faced a similar problem? Any advice at all?

  • stéfan

    thanks a lot for this tutorial very clear

Store Top Sales

You like the tuts and want to say "thank you"? Well, you can always feel free to donate: