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.

<br />
&lt;?php</p>
<p>if (!defined('_PS_VERSION_'))<br />
	exit;</p>
<p>class newFieldsTut extends Module<br />
{<br />
	/* @var boolean error */<br />
	protected $_errors = false;</p>
<p>	public function __construct()<br />
	{<br />
		$this-&gt;name = 'newfieldstut';<br />
		$this-&gt;tab = 'front_office_features';<br />
		$this-&gt;version = '1.0';<br />
		$this-&gt;author = 'Nemo';<br />
		$this-&gt;need_instance = 0;</p>
<p>	 	parent::__construct();</p>
<p>		$this-&gt;displayName = $this-&gt;l('New Fields Tutorial');<br />
		$this-&gt;description = $this-&gt;l('Test module from Nemo\'s Post Scriptum Tutorial (nemops.com).');<br />
	}</p>
<p>}<br />
?&gt;<br />

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:

</p>
<p>	public function install()<br />
	{<br />
		if (!parent::install() OR<br />
			!$this-&gt;alterTable('add') OR<br />
			!$this-&gt;registerHook('actionAdminControllerSetMedia') OR<br />
			!$this-&gt;registerHook('actionProductUpdate') OR<br />
			!$this-&gt;registerHook('displayAdminProductsExtra'))<br />
			return false;<br />
		return true;<br />
	}</p>
<p>	public function uninstall()<br />
	{<br />
		if (!parent::uninstall() OR !$this-&gt;alterTable('remove'))<br />
			return false;<br />
		return true;<br />
	}</p>
<p>	public function alterTable($method)<br />
	{<br />
		switch ($method) {<br />
			case 'add':<br />
				$sql = 'ALTER TABLE ' . _DB_PREFIX_ . 'product_lang ADD `custom_field` TEXT NOT NULL';<br />
				break;</p>
<p>			case 'remove':<br />
				$sql = 'ALTER TABLE ' . _DB_PREFIX_ . 'product_lang DROP COLUMN `custom_field`';<br />
				break;<br />
		}</p>
<p>		if(!Db::getInstance()-&gt;Execute($sql))<br />
			return false;<br />
		return true;<br />
	}<br />

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:

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

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:

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

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:

</p>
<p>	public function prepareNewTab()<br />
	{</p>
<p>		$this-&gt;context-&gt;smarty-&gt;assign(array(<br />
			'custom_field' =&gt; '',<br />
			'languages' =&gt; $this-&gt;context-&gt;controller-&gt;_languages,<br />
			'default_language' =&gt; (int)Configuration::get('PS_LANG_DEFAULT')<br />
		));</p>
<p>	}</p>
<p>

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:

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

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:

<br />
$(document).ready(function() {</p>
<p>});<br />

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

</p>
<p>	$(&quot;#product-tab-content-ModuleNewfieldstut&quot;).on('loaded', function(){<br />
			displayFlags(languages, id_language, allowEmployeeFormLang);<br />
	})</p>
<p>

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:

<br />
	public function hookActionAdminControllerSetMedia($params)<br />
	{</p>
<p>		// add necessary javascript to products back office<br />
		if($this-&gt;context-&gt;controller-&gt;controller_name == 'AdminProducts' &amp;&amp; Tools::getValue('id_product'))<br />
		{<br />
			$this-&gt;context-&gt;controller-&gt;addJS($this-&gt;_path.'/js/newfieldstut.js');<br />
		}</p>
<p>	}<br />

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 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:

</p>
<p>	public function hookActionProductUpdate($params)<br />
	{<br />
		// get all languages<br />
		// for each of them, store the new field</p>
<p>		$id_product = (int)Tools::getValue('id_product');</p>
<p>		$languages = Language::getLanguages(true);<br />
		foreach ($languages as $lang) {<br />
			if(!Db::getInstance()-&gt;update('product_lang', array('custom_field'=&gt; pSQL(Tools::getValue('custom_field_'.$lang['id_lang']))) ,'id_lang = ' . $lang['id_lang'] .' AND id_product = ' .$id_product ))<br />
				$this-&gt;context-&gt;controller-&gt;_errors[] = Tools::displayError('Error: ').mysql_error();<br />
		}</p>
<p>	}</p>
<p>

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:

</p>
<p>	public function getCustomField($id_product)<br />
	{<br />
		$result = Db::getInstance()-&gt;ExecuteS('SELECT custom_field, id_lang FROM '._DB_PREFIX_.'product_lang WHERE id_product = ' . (int)$id_product);<br />
		if(!$result)<br />
			return array();</p>
<p>		foreach ($result as $field) {<br />
			$fields[$field['id_lang']] = $field['custom_field'];<br />
		}</p>
<p>		return $fields;<br />
	}</p>
<p>

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:

</p>
<p>	public function prepareNewTab()<br />
	{</p>
<p>		$this-&gt;context-&gt;smarty-&gt;assign(array(<br />
			'custom_field' =&gt; $this-&gt;getCustomField((int)Tools::getValue('id_product')),<br />
			'languages' =&gt; $this-&gt;context-&gt;controller-&gt;_languages,<br />
			'default_language' =&gt; (int)Configuration::get('PS_LANG_DEFAULT')<br />
		));</p>
<p>	}</p>
<p>

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

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

4 Comments on Adding New Tabs and Fields to PrestaShop Products’ Back Office

  1. Vij says:

    im want to add one custom fields in admin attribute value page……

    somehow i manage to add field in attribute value page but im not able to save and update the custom field value.

    any help woulld be appritiated..

  2. H says:

    Nice tutorial, seems to be a much cleaner way of extending prestashop! Just one question though…
    will you be able to access the custom field in all tpl’s as you would an original field? For example {$product->custom_field} or in this case {$product_lang->custom_field}

  3. Rake says:

    Hi, please help me how to add “Location” and “supplier reference” in Product global information Prestashop 1.5.6.2 and 1.6.x ?
    Thank you

Leave a Reply

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


5 + 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>