Adding new tabs and fields to Prestashop products’ back office

It is easy enough to modify Prestashop’s products’ back office and add new fields by editing the AdminProductsController. However, by using a module and a new tab, it becomes much more maintainable!

Download Project Files

Prestashop back office hooks

Although we will go through the basics of setting up a module, this tutorial assumes you already know how to create one from scratch. If you are totally new to Prestashop, I strongly recommend that you read the Official documentation on how to create a Prestashop Module before approaching this tutorial.

Now, in order to add new fields to Prestashop’s products’ back office, it is essential to be aware of the hooks that can be used to perform various operations in the admin panel. Specifically, we will be using the following ones:

DisplayAdminProductsExtra: this hook adds new tabs to the product’s back office interface, thus, we will use this hook to plug in our new content’s input field with translations

ActionAdminControllerSetMedia: if we ever need to style our new tab, or add custom javascript functions, this is the ideal hook to use to plug css and js files. This is called for every Prestashop Admin page, thus we will need to prevent it from running in pages that are not the product’s configuration.

ActionProductUpdate: the new field needs to be saved in the database. Specifically, we will add a new column (custom_field) to the product_lang table and add a new translatable field, without using overrides at all. By plugging code snippets into this hook, we can easily update the column when the product itself is updated.

At this point, we can line out our plan of action: after giving the module a basic setup, we will first create the template file that will display the new field’s input box on its own table. Then, we will see how to overcome a little issue with translatable fields, using the ActionAdminControllerSetMedia hook, and lastly, on data submission, we will update the new custom field once the product is updated. We will create a new field for demonstrational purposes only, but feel free to dare beyond the limits of this tutorial if you want!

Setting up the module

First of all, let’s start with a simple module template. You can download the blank module folder from the link at the beginning of the article. Drop the newfieldstut folder (the one from the “Start here” archive!) in the modules/ folder of your Prestashop Root. Then open up newfieldstut.php and start by adding some regular module’s code.

<?php

if (!defined('_PS_VERSION_'))
	exit;

class newFieldsTut extends Module
{
	/* @var boolean error */
	protected $_errors = false;
	
	public function __construct()
	{
		$this->name = 'newfieldstut';
		$this->tab = 'front_office_features';
		$this->version = '1.0';
		$this->author = 'Nemo';
		$this->need_instance = 0;

	 	parent::__construct();

		$this->displayName = $this->l('New Fields Tutorial');
		$this->description = $this->l('Test module from Nemo\'s Post Scriptum Tutorial (nemops.com).');
	}
		
}
?>

Now that the basic structure is laid out, we want to specify the install and uninstall functions. Let’s add a new column to product_lang with an ALTER TABLE, and provide a way to remove it if this module is uninstalled:


	public function install()
	{
		if (!parent::install() OR
			!$this->alterTable('add') OR			
			!$this->registerHook('actionAdminControllerSetMedia') OR
			!$this->registerHook('actionProductUpdate') OR
			!$this->registerHook('displayAdminProductsExtra'))
			return false;
		return true;
	}
	
	public function uninstall()
	{
		if (!parent::uninstall() OR !$this->alterTable('remove'))
			return false;
		return true;
	}


	public function alterTable($method)
	{
		switch ($method) {
			case 'add':
				$sql = 'ALTER TABLE ' . _DB_PREFIX_ . 'product_lang ADD `custom_field` TEXT NOT NULL';
				break;
			
			case 'remove':
				$sql = 'ALTER TABLE ' . _DB_PREFIX_ . 'product_lang DROP COLUMN `custom_field`';
				break;
		}
		
		if(!Db::getInstance()->Execute($sql))
			return false;
		return true;
	}

Explanation: A couple of things here! First, in the install method, we

  • 1- As with every module, call the parent’s install method
  • 2- Alter the product_lang table, to add the new field
  • 3- Register all the hooks we will use

Notice how I decided to structure the alterTable method. To avoid creating two functions, I stacked together the drop and add statements, passing a $method variable to determine what the function should do. Therefore, we call alterTable with ‘add’ when installing, and ‘remove’ when uninstalling.

Now, just to be sure we did everything correctly without errors, let’s try installing the module! Reach the Modules page in the back office and look for New Fields Tutorial in the modules list, as shown below.

Our module in the Prestashop modules list

Click install, and see if it works. If it does, move on to the next step!

Creating a new tab in the Product Back Office (DisplayAdminProductsExtra)

Okay, our module is installed, and the hooks have been registered as well. Time to test it out! Add the following function to your module:

	public function hookDisplayAdminProductsExtra($params)
	{
		if (Validate::isLoadedObject($product = new Product((int)Tools::getValue('id_product'))))
		{

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

Explanation: first, we make sure the current product object is correctly loaded; if so we display the content of the module’s tpl file. Reach a product’s back office page; you should see something like this, by clicking on the new tab:

New tab to Prestashop Products' back office

At this point, we can start filling in the tpl file. Open up newfieldstut.tpl, get rid of the placeholder text and add the following instead:


<h4>{l s='New Fields Tutorial' mod='newfieldstut'}</h4>
<div class="separation"></div>
<table>
	<tr>
		<td class="col-left">
			<label>{l s='Our New Field:'}</label>
		</td>
		<td>
			{include file="controllers/products/input_text_lang.tpl"
				languages=$languages
				input_name='custom_field'
				input_value=$custom_field}
			<p class="preference_description">{l s='Simply a new custom field'}. <strong>{l s='ie: something here as a hint'}</strong></p>
		</td>
	</tr>
</table>

Do not test this out yet! It will not work. Why? Let’s have a look at what we just did: first, we setup a simple table structure imitating the one Prestashop uses by default in the Product configuration page. Then, inside the rightmost table cell (td), we include a template file Prestashop kindly provides. This file is input_text_lang.tpl, and it is located in the admin theme folder, inside the controller section for products. What it does, is it provides a standardized way to add new translatable fields without having to mess with foreach loops in our own code. We can assign the currently available languages, name of the new input field and its value.

However, if we were to refresh the page now, and turn on error reporting, this is what we would see:

Error before adding variables to the hook

This happens because we have not assigned any of those 2 variables we mention in the code: $languages and $custom_field. Let’s take care of this.

Back to newfieldstut.php, create a new method as follows:


	public function prepareNewTab()
	{

		$this->context->smarty->assign(array(
			'custom_field' => '',
			'languages' => $this->context->controller->_languages,
			'default_language' => (int)Configuration::get('PS_LANG_DEFAULT')
		));

	}

For the time being, we will not bother adding a real value to the new field. As for the other 2 variables, we get the currently available languages by the controller itself, and the default language for our store.

At this point, amend hookDisplayAdminProductsExtra as follows:

	public function hookDisplayAdminProductsExtra($params)
	{
		if (Validate::isLoadedObject($product = new Product((int)Tools::getValue('id_product'))))
		{
			$this->prepareNewTab();
			return $this->display(__FILE__, 'newfieldstut.tpl');
		}
	}	

The new tab is ready! ….BUT! Hey, watch closely at what happens if you wait a couple of seconds before accessing it, or click its label right after the page reloads!

The language flags pops out after a while, or does not appear at all!

This is (I believe) yet another Prestashop bug. If we want our new translatable field to work correctly every time, we absolutely need to take this bug off. How to?

Properly enabling multilanguage fields in the product Back Office

What on earth is going on here? Basically, Prestashop loads the language translations flags using javascript. Not only this, but it also loads each tab asynchronously with Ajax, and ‘forgets’ to call the function which assigns those flags for custom tabs. This is where the ActionAdminControllerSetMedia hook becomes useful! We will plug in a new javascript file to take it into account, so that flags are assigned to our new tab as well.

First off, open up newfieldstut.js, which you can find in the project’s module folder you already know. Here is how it will look:

$(document).ready(function() {

});

Nothing but a simple jQuery ready statement. Inside, add the following


	$("#product-tab-content-ModuleNewfieldstut").on('loaded', function(){
			displayFlags(languages, id_language, allowEmployeeFormLang);
	})

And that’s it, really. A few notes though: Notice the ID we are checking the load status for. This will vary depending on the name you give your module, and you can check it by inspecting the tab element with the browser’s developer tools:

Inspecting the Product tab id in the back office

Just bear in mind you have to point the right ID, nothing else.

Now, back to newfieldstut.php once more. Add the following method:

	public function hookActionAdminControllerSetMedia($params)
	{

		// add necessary javascript to products back office
		if($this->context->controller->controller_name == 'AdminProducts' && Tools::getValue('id_product'))
		{
			$this->context->controller->addJS($this->_path.'/js/newfieldstut.js');
		}

	}

Explanation: As we saw at the beginning of the article, this hook runs for every page of the back office. Thus, we need to be sure we are embedding our javascript for the single product configuration page only. Therefore, we first check the the current controller is AdminProducts, then, to be sure we are not viewing the products list, we also make sure a product id is set in the current url. After performing these checks, we simply use a regular addJS to embed the code.

Save & refresh, at this point the language flags are always added to the new tab!

Saving and retrieving the new value

Everything is in place, but we are not doing anything now. We are not displaying the new field value, nor are we able to save its content!

First off, let’s save the value for each language inside the database. Append the following method to our beloved newfieldstut.php:


	public function hookActionProductUpdate($params)
	{
		// get all languages
		// for each of them, store the new field

		$id_product = (int)Tools::getValue('id_product');

		$languages = Language::getLanguages(true);
		foreach ($languages as $lang) {
			if(!Db::getInstance()->update('product_lang', array('custom_field'=> pSQL(Tools::getValue('custom_field_'.$lang['id_lang']))) ,'id_lang = ' . $lang['id_lang'] .' AND id_product = ' .$id_product ))
				$this->context->controller->_errors[] = Tools::displayError('Error: ').mysql_error();
		}

	}

Explanation: the hook we are targeting here(ActionProductUpdate) runs at the very same time a product’s data is stored inside the database. The perfect time to add our new content! First, we retrieve the current product ID, and then get all the currently active languages. For each of the languages, we update the database field for the matching language and product id. If the update operation cannot be completed, we spawn an error using the default Prestashop error handler for Admin tabs.

A small note on Tools::getValue(‘custom_field_’.$lang[‘id_lang’]): if you remember, we called our new field ‘custom_field’ when embedding the translatable input box. This means Prestashop generates one field, appending each language id, for each language. Thus, we end up having custom_field_1, custom_field_2 etc… Where the number after the second underscore represents the language ID. That’s why we are targeting it like this ‘custom_field_’.$lang[‘id_lang’], to be sure each language is correctly added.

Now, let’s try inputting different values for each languages, and hit ‘Save and stay’. Then, since we are not yet retrieving the field, let’s check it out in the database.

Our custom fields in the Prestashop Database

Okay, it’s done! At this point, we only need to retrieve those values! Let’s add a new method to our friend newfieldstut.php:


	public function getCustomField($id_product)
	{
		$result = Db::getInstance()->ExecuteS('SELECT custom_field, id_lang FROM '._DB_PREFIX_.'product_lang WHERE id_product = ' . (int)$id_product);
		if(!$result)
			return array();

		foreach ($result as $field) {
			$fields[$field['id_lang']] = $field['custom_field'];
		}

		return $fields;
	}

Explanation: Quite simply, using the product id as source data, we retrieve all entries for the custom_field column. Then, we format it for an array whose keys are language ids, and values the custom field value for that specific language. This is how Prestashop will be able to use them in the template: array(‘5′ => ‘value for id_lang 5), …’).

Of course, as a last step we call this function when assigning variables in prepareNewTab:


	public function prepareNewTab()
	{

		$this->context->smarty->assign(array(
			'custom_field' => $this->getCustomField((int)Tools::getValue('id_product')),
			'languages' => $this->context->controller->_languages,
			'default_language' => (int)Configuration::get('PS_LANG_DEFAULT')
		));

	}

And we are finished!

The new custom field added to Prestashop Products' back office

Conclusion

As you could see, adding new fields to products using modules, without any remote sign of overrides, is not as complex as it might seem. Moreover, it’s a much more maintainable practice than messing with the original back office templates and controllers! Of course, at this point, you can take this tutorial further and add front office hooks to display your new, translatable field!

 

Additional Resources

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

  • Dinesh Kumar

    How can i display the extra tab value on product page?

  • Dinesh Kumar

    I tried this module, its really helpful for my project and also its working fine.

  • Boris

    Hi Nemo,

    I’m on PS 1.6 and I use your module with success but I only see one small bug…

    I found how to use Tinymce on the textearea.

    Add class=”rte” and some javascript :

    $(function() {
    tinySetup();
    });

    But when I click on the new field, it show each input for each language… not just the default… and when I click on “save and stay”, page is refreshed and shown with the good one input with the good language…

    Have you an idea ?

    Anyway, great module, thank you !

    • NemoPS

      Hmmm it might be missing id_language as js variable. You might need to add this hideOtherLanguage(id_language)

  • andrew

    Hi Nemo,
    Is it possible that the custom_field content to appear into pdf invoice that i generate in BO? Thanx!

    • NemoPS

      It is, but definitely complex. Depending on where you need it, you will have to edit the OrderInvoice method that gets products, as well as the HTMLTemplateInvoice.php and pdf/invoice,tpl

      • andrew

        ahh.. Look .. i need to generate a warranty file (add new page to the pdf invoice).
        And the new page should contain 1. Name of the product and 2. An unique serial number defined by manufacturer (which i have to insert manually). Do you have any idea how can i do this?

        • NemoPS

          Complex. You need a new method in the AdminPDFController class that triggers a new html template (say, call it HTMLTemplateWarranty) that loads a new template located in the main pdf/ folder
          In the HTMLTemplateWarranty file, you can load all the files you need, but it’s still going to be bound to an order

  • Mickael

    Hi, did you find a solution ? I have the same problem..

  • Nguyễn Hưởng

    How can to add the save button?

  • NemoPS

    Strange, check if you have any javascript error on the page

    • Mickael

      in the tpl,

      replace the right file and on 1.6 add class “autoload_rte” allow to view the textarea with the tinyMCE…

      {include file=”controllers/products/textarea_lang.tpl”
      languages=$languages
      class=”autoload_rte”
      input_name=’custom_field’
      input_value=$custom_field}

      But from the textarea, it still not possible to save the content in HTML, it save it in plain text..
      It’s probably related to the php file during update at this line..

      if(!Db::getInstance()->update(‘product_lang’, array(‘custom_field’=> pSQL(Tools::getValue(‘custom_field_’.$lang[‘id_lang’]))) ,’id_lang = ‘ . $lang[‘id_lang’] .’ AND id_product = ‘ .$id_product ))

      but not idea what to do… If someone have a suggestion…

      • Paweł Kotarski

        maybe someone helps: you need to add TRUE as a second param to pSQL function

        if(!Db::getInstance()->update(‘product_lang’, array(‘custom_field’=> pSQL(Tools::getValue(‘custom_field_’.$lang[‘id_lang’]),true)) ,’id_lang = ‘ . $lang[‘id_lang’] .’ AND id_product = ‘ .$id_product ))

  • Sridhar Mundra

    Hi . . Now i know thats a rookie’s question but i am new to prestashop and php.
    I have registered a hook in the desired place in product.tpl. Can some1 guide me how to display the contents of the field in that hook?

  • tree

    Hello Nemo,

    I follow this steps in PS v.1.6.

    After added the function hookActionProductUpdate,I cannot see the button “save and stay” in my back office.

    So I couldn’t add a field in the product_lang.

    Should I add any code in the version 1.6?

    • NemoPS

      Does it get back if you remove the hook? It’s odd, that is no visual hook, it should not affect your visuals

      • tree

        Thank you for your reply.

        Do you mean unhook and re-hook the module? It didn’t work.

    • Marianne

      I have nearly the same problem, there is no button to save the field (no flags either, but less important at this point) PS 1.6.1.1 would be great if we get it to work!

  • NemoPS

    Hi there!
    What do you exactly mean by didn’t work? Spawning any error? The validation type of the array should really work

    • jeanjeanjeana

      Hello !!

      Thanks for your quick answer !!

      I put my verification in public “function hookActionProductUpdate($params)” just after the foreach so that if the validation isn’t ok, the database didn’t update ! and i put this for the verification :

      if(!Validate::isInt($custom_field)
      {
      DiplayError…..
      }
      else
      {
      Update the database
      }

      Firt I don’t know if my verification realy has to go in this class and then when i submit, the database never update even if my entre is an INT… and my display Error spawn no error.. I think I have the wrong syntaxe !

      (I’m sorry for the spelling mistakes im not native English)

      Thank you in advance ! =)

      • jeanjeanjeana

        Solved my problem alone =)

        Thank you anyway =)

  • jeanjeanjeana

    Hello,

    I would like to add a form verification for the “custom_field”, for exemple just int or just text, I tried with validate.php who is in prestashop but i didn’t work… If someone has an idea or a link it will be very nice ! =)

    Thanks !

  • slonn

    How generate the form with HelperForm?

  • Vijay

    Can we resolve the compability issue with PS 1.6 & this module? Right now tab was not loaded in PS 1.6.

  • Wotek

    how can i active TinyMce in the textarea ?

    • http://blog.eaposztrof.com eaposztrof

      adding the class=”autoload_rte” can solve this.

      • armez

        And how to add class=”autoload_rte” to this textarea?

        • NemoPS

          You just have to add it in the html

  • Wotek

    Hi

    I have problem with add two extra fields.
    On admin page views the same value in both fields.
    How can i change ?

    Best regards
    Wojtek

  • b_pcmaster

    hey nimo…nimo can you please guide me how to add this module too product.tpl file? i tryed {$custom_field} inside my product tpl and it shows nothing so whats the point if you add some field to your admin area but cant show them to user?please let me know how to…thx

  • http://ps13.org maio

    hi nemo,

    I don’ get any errors even if I don’t create the ‘ public function prepareNewTab() ‘ method,

    I can make too many guesses on why this is happening….

    I’m using last PS V.1.6.9…
    what do you think about it?

    • NemoPS

      well it can be anything really, did you enable error display?

      • http://ps13.org maio

        sure

        I still haven’t had a lot of time ot try but it probably depends on tpl structure or the backoffice of 1.6,

        as soon as I try I’ll give you feedback,

  • Com

    I need help!
    Hi, how I can display the new field in product-list.tpl

  • Nishant Vadgama

    how can I validate data on hookActionProductUpdate() and return error if exist..? with your code I have written ” $this->context->controller->_errors[]” but this is not display error,

  • Wassim Jied

    Great !

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