How to Create New Pages in Prestashop

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 visible.

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 at 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 useful 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 instantiated  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 time 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 possibilities 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.

Need Prestashop Modules? Have a look at my Prestashop Addons Store!

“Looking for quality PrestaShop Web Hosting? Look no further than Arvixe Web Hosting!”

Tags: , , , , , | Posted under PrestaShop | RSS 2.0

Author Spotlight

Fabio Porta

Fabio Porta

Fabio has been involved in web development and design since 2005, when launched his first website at the age of 16. He’s now highly skilled in both client and server side development, along with design, and since August 2012 runs a successful website about PrestaShop tutorials and Prestashop Modules called Nemo’s Post Scriptum, at http://nemops.com

10 Comments on How to Create New Pages in Prestashop

  1. mc418a says:

    you save my day with step 7, thanks :)

  2. ileriya says:

    Thank you! Very detailed description

  3. Nach says:

    Hi,

    Thanks it’s a great tutorial. I’m applying it in ps 1.6 and I’m stuck in the add template step, once I add it I get a 500 server error. It should be some change in the 1.6 structure, do you have any idea about it?

    Thanks!

  4. nguyễn thanh toàn says:

    first, thank your post! i cannot add compare button and sort into this page.
    Please help me!
    Thanks advance!
    i use prestashop 1.5.

  5. truongdx says:

    first, thank your post! i am writing a module, you can help me create an page (in my module) display in back-office with content is HelloWorld. Please help me! Thanks advance! i use prestashop 1.6

      • truongdx says:

        thank you for reply,my module have 3 files: create_page_test.php, AdminCreatePageTestController.php , content.tpl
        — create_page_test.php
        ‘AdminCreatePageTestController’,
        ‘name’ => ‘Create Page Test’,
        )
        );

        public function __construct()
        {
        $this->name = ‘create_page_test';
        $this->tab = ‘front_office_features';
        $this->version = ‘1.0.0’;
        $this->author = ‘TruongDX_Test';
        $this->need_instance = 0;
        $this->is_configurable = 0;
        $this->module_key = “”;

        parent::__construct();

        $this->displayName = $this->l(‘Page Second’);
        $this->description = $this->l(‘Create Page Second’);
        }
        public function install()
        {
        if (!parent::install())
        {
        return FALSE;
        }

        // $sql = array();
        // include (dirname(__file__) . ‘/init/install_sql.php’);
        // foreach ($sql as $s) {
        // if (!Db::getInstance()->Execute($s)) {
        // return FALSE;
        // }
        // }

        foreach ($this->tab_array as $tab) {
        $new_tab = new Tab();
        $new_tab->class_name = $tab['class_name'];
        $new_tab->id_parent = Tab::getCurrentParentId();
        $new_tab->module = $this->name;
        $languages = Language::getLanguages();
        foreach ($languages as $language) {
        $new_tab->name[$language['id_lang']] = $tab['name'];
        }

        $new_tab->add();
        }

        return TRUE;
        }
        public function uninstall()
        {
        if (!parent::uninstall()) {
        return FALSE;
        }

        // $sql = array();
        // include (dirname(__file__) . ‘/init/uninstall_sql.php’);
        // foreach ($sql as $s) {
        // if (!Db::getInstance()->Execute($s)) {
        // return FALSE;
        // }
        // }

        $idTabs = array();
        $idTabs[] = Tab::getIdFromClassName(‘AdminCreatePageSecond’);
        //$idTabs[] = Tab::getIdFromClassName(‘AdminFAQBlock’);
        foreach ($idTabs as $idTab) {
        if ($idTab) {
        $tab = new Tab($idTab);
        $tab->delete();
        }
        }

        return TRUE;
        }
        }

        –AdminCreatePageTestController.php
        context->smarty;
        $smarty->assign(‘test’, ‘Hello!’);
        }
        }

        –content.tpl
        {$test}

        i don’t know, how to display text ‘Hello’ then click tab ‘Create Page Test’ in back-office
        please help me, thank you very much!

  6. truongdx says:

    hi, it look like : http://i.imgur.com/bhMJpe5.png
    can you help me display content to space white? Thank you very much!

Leave a Reply

Your email address will not be published. Required fields are marked *


8 × = 8

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>