Adding a second image for Prestashop categories

Prestashop only allows using one image as category thumbnail by default, thus creating issues in having different formats for subcategory images in the product list view. In this tutorial we will add a secondary image to prestashop categories, to increase flexibility on thumbs display when showing them as subcategories.

  • Prestashop version: any 1.6 (used: 1.6.0.9, should equally apply to 1.5, though not tested)
Download Project Files

Introduction

Before starting, it’s worth noticing this tutorial implies quite a lot of modifications and overrides, and it’s therefore recommended that you have a solid knowledge of php before doing anything. As always, refer to the Official Prestashop documentation on Overrides if you are new to the subject. That said, these are the files we need to override/modify:

  • AdminCategoriesController.php: here is where most of the magic will occur, to upload and delete the new image
  • Category.php: the category class, as we have to implement the new field and image file deletion
  • category.tpl: to display the new image for subcategories
  • .htaccess: only needed if you use Friendly urls, otherwise no image will be shown

Extending the AdminCategoriesController file

The categories’ admin controller is the file we will be mostly dealing with. Although we could modify it directly, it’s always best practice to use an override. Therefore, create a new file in /override/controllers/admin named AdminCategoriesController.php, and paste the following inside php tags:

class AdminCategoriesController extends AdminCategoriesControllerCore
{
}

Then, reach the original AdminCategoriesController located within /controllers/admin, open it, locate and copy the whole renderForm method. Then, paste it inside the new override:



class AdminCategoriesController extends AdminCategoriesControllerCore
{


	public function renderForm()
	{
		$this->initToolbar();
		$obj = $this->loadObject(true);
		$id_shop = Context::getContext()->shop->id;
		$selected_categories = array((isset($obj->id_parent) && $obj->isParentCategoryAvailable($id_shop))? (int)$obj->id_parent : (int)Tools::getValue('id_parent', Category::getRootCategory()->id));
		$unidentified = new Group(Configuration::get('PS_UNIDENTIFIED_GROUP'));
		$guest = new Group(Configuration::get('PS_GUEST_GROUP'));
		$default = new Group(Configuration::get('PS_CUSTOMER_GROUP'));

		$unidentified_group_information = sprintf($this->l('%s - All people without a valid customer account.'), '<b>'.$unidentified->name[$this->context->language->id].'</b>');
		$guest_group_information = sprintf($this->l('%s - Customer who placed an order with the guest checkout.'), '<b>'.$guest->name[$this->context->language->id].'</b>');
		$default_group_information = sprintf($this->l('%s - All people who have created an account on this site.'), '<b>'.$default->name[$this->context->language->id].'</b>');

		if (!($obj = $this->loadObject(true)))
			return;

		$image = _PS_CAT_IMG_DIR_.$obj->id.'.jpg';
		$image_url = ImageManager::thumbnail($image, $this->table.'_'.(int)$obj->id.'.'.$this->imageType, 350,
			$this->imageType, true, true);
		$image_size = file_exists($image) ? filesize($image) / 1000 : false;

		$this->fields_form = array(
			'tinymce' => true,
			'legend' => array(
				'title' => $this->l('Category'),
				'icon' => 'icon-tags'
			),
			'input' => array(
				array(
					'type' => 'text',
					'label' => $this->l('Name'),
					'name' => 'name',
					'lang' => true,
					'required' => true,
					'class' => 'copy2friendlyUrl',
					'hint' => $this->l('Invalid characters:').' <>;=#{}',
				),
				array(
					'type' => 'switch',
					'label' => $this->l('Displayed'),
					'name' => 'active',
					'required' => false,
					'is_bool' => true,
					'values' => array(
						array(
							'id' => 'active_on',
							'value' => 1,
							'label' => $this->l('Enabled')
						),
						array(
							'id' => 'active_off',
							'value' => 0,
							'label' => $this->l('Disabled')
						)
					)
				),
				array(
					'type'  => 'categories',
					'label' => $this->l('Parent category'),
					'name'  => 'id_parent',
					'tree'  => array(
						'id'                  => 'categories-tree',
						'selected_categories' => $selected_categories,
						'disabled_categories' => !Tools::isSubmit('add'.$this->table) ? array($this->_category->id) : null
					)
				),
				array(
					'type' => 'textarea',
					'label' => $this->l('Description'),
					'name' => 'description',
					'autoload_rte' => true,
					'lang' => true,
					'hint' => $this->l('Invalid characters:').' <>;=#{}'
				),
				array(
					'type' => 'file',
					'label' => $this->l('Image'),
					'name' => 'image',
					'display_image' => true,
					'image' => $image_url ? $image_url : false,
					'size' => $image_size,
					'delete_url' => self::$currentIndex.'&'.$this->identifier.'='.$this->_category->id.'&token='.$this->token.'&deleteImage=1',
					'hint' => $this->l('Upload a category logo from your computer.'),
				),
				array(
					'type' => 'text',
					'label' => $this->l('Meta title'),
					'name' => 'meta_title',
					'lang' => true,
					'hint' => $this->l('Forbidden characters:').' <>;=#{}'
				),
				array(
					'type' => 'text',
					'label' => $this->l('Meta description'),
					'name' => 'meta_description',
					'lang' => true,
					'hint' => $this->l('Forbidden characters:').' <>;=#{}'
				),
				array(
					'type' => 'tags',
					'label' => $this->l('Meta keywords'),
					'name' => 'meta_keywords',
					'lang' => true,
					'hint' => $this->l('To add "tags," click in the field, write something, and then press "Enter."').'&nbsp;'.$this->l('Forbidden characters:').' <>;=#{}'
				),
				array(
					'type' => 'text',
					'label' => $this->l('Friendly URL'),
					'name' => 'link_rewrite',
					'lang' => true,
					'required' => true,
					'hint' => $this->l('Only letters, numbers, underscore (_) and the minus (-) character are allowed.')
				),
				array(
					'type' => 'group',
					'label' => $this->l('Group access'),
					'name' => 'groupBox',
					'values' => Group::getGroups(Context::getContext()->language->id),
					'info_introduction' => $this->l('You now have three default customer groups.'),
					'unidentified' => $unidentified_group_information,
					'guest' => $guest_group_information,
					'customer' => $default_group_information,
					'hint' => $this->l('Mark all of the customer groups which you would like to have access to this category.')
				)
			),
			'submit' => array(
				'title' => $this->l('Save'),
				'name' => 'submitAdd'.$this->table.($this->_category->is_root_category && !Tools::isSubmit('add'.$this->table) && !Tools::isSubmit('add'.$this->table.'root') ? '': 'AndBackToParent')
			)
		);

		$this->tpl_form_vars['shared_category'] = Validate::isLoadedObject($obj) && $obj->hasMultishopEntries();
		$this->tpl_form_vars['PS_ALLOW_ACCENTED_CHARS_URL'] = (int)Configuration::get('PS_ALLOW_ACCENTED_CHARS_URL');
		$this->tpl_form_vars['displayBackOfficeCategory'] = Hook::exec('displayBackOfficeCategory');

		// Display this field only if multistore option is enabled
		if (Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE') && Tools::isSubmit('add'.$this->table.'root'))
		{
			$this->fields_form['input'][] = array(
				'type' => 'switch',
				'label' => $this->l('Root Category'),
				'name' => 'is_root_category',
				'required' => false,
				'is_bool' => true,
				'values' => array(
					array(
						'id' => 'is_root_on',
						'value' => 1,
						'label' => $this->l('Yes')
					),
					array(
						'id' => 'is_root_off',
						'value' => 0,
						'label' => $this->l('No')
					)
				)
			);
			unset($this->fields_form['input'][2],$this->fields_form['input'][3]);
		}
		// Display this field only if multistore option is enabled AND there are several stores configured
		if (Shop::isFeatureActive())
			$this->fields_form['input'][] = array(
				'type' => 'shop',
				'label' => $this->l('Shop association'),
				'name' => 'checkBoxShopAsso',
			);

		// remove category tree and radio button "is_root_category" if this category has the root category as parent category to avoid any conflict
		if ($this->_category->id_parent == Category::getTopCategory()->id && Tools::isSubmit('updatecategory'))
			foreach ($this->fields_form['input'] as $k => $input)
				if (in_array($input['name'], array('id_parent', 'is_root_category')))
					unset($this->fields_form['input'][$k]);

		if (!($obj = $this->loadObject(true)))
			return;

		$image = ImageManager::thumbnail(_PS_CAT_IMG_DIR_.'/'.$obj->id.'.jpg', $this->table.'_'.(int)$obj->id.'.'.$this->imageType, 350, $this->imageType, true);

		$this->fields_value = array(
			'image' => $image ? $image : false,
			'size' => $image ? filesize(_PS_CAT_IMG_DIR_.'/'.$obj->id.'.jpg') / 1000 : false
		);

		// Added values of object Group
		$category_groups_ids = $obj->getGroups();

		$groups = Group::getGroups($this->context->language->id);
		// if empty $carrier_groups_ids : object creation : we set the default groups
		if (empty($category_groups_ids))
		{
			$preselected = array(Configuration::get('PS_UNIDENTIFIED_GROUP'), Configuration::get('PS_GUEST_GROUP'), Configuration::get('PS_CUSTOMER_GROUP'));
			$category_groups_ids = array_merge($category_groups_ids, $preselected);
		}
		foreach ($groups as $group)
			$this->fields_value['groupBox_'.$group['id_group']] = Tools::getValue('groupBox_'.$group['id_group'], (in_array($group['id_group'], $category_groups_ids)));

		$this->fields_value['is_root_category'] = (bool)Tools::isSubmit('add'.$this->table.'root');

		return parent::renderForm();
	}
	
}

Notice the last row, return parent::renderForm();. Change it to return AdminController::renderForm();. if we didn’t take this counter-measure, all of our changes would have been overridden by the original controller. To make sure the override works, reach the cache/ and erase class_index.php to enable the new file. Then login to the back office, and check the single category view works as expected. If so, read on.

Adding the new field

First off, we will need to add the code that displays any eventual image we upload. To do so, locate the following:

		$image = _PS_CAT_IMG_DIR_.$obj->id.'.jpg';
		$image_url = ImageManager::thumbnail($image, $this->table.'_'.(int)$obj->id.'.'.$this->imageType, 350,
			$this->imageType, true, true);
		$image_size = file_exists($image) ? filesize($image) / 1000 : false;

Right after it, add

		$image2 = _PS_CAT_IMG_DIR_.$obj->id.'_second.jpg';
		$image_url2 = ImageManager::thumbnail($image2, $this->table.'_'.(int)$obj->id.'_second.'.$this->imageType, 350,
			$this->imageType, true, true);
		$image_size2 = file_exists($image2) ? filesize($image2) / 1000 : false;

Which is basically the same with modified names. Next, right below we have the fields list. We need to add our file input here, so that we can later upload the image. Therefore, locate:

				array(
					'type' => 'file',
					'label' => $this->l('Image'),
					'name' => 'image',
					'display_image' => true,
					'image' => $image_url ? $image_url : false,
					'size' => $image_size,
					'delete_url' => self::$currentIndex.'&'.$this->identifier.'='.$this->_category->id.'&token='.$this->token.'&deleteImage=1',
					'hint' => $this->l('Upload a category logo from your computer.'),
				),

And add the following right after it

				array(
					'type' => 'file',
					'label' => $this->l('Image2'),
					'name' => 'image2',
					'display_image' => true,
					'image' => $image_url2 ? $image_url2 : false,
					'size' => $image_size2,
					'delete_url' => self::$currentIndex.'&'.$this->identifier.'='.$this->_category->id.'&token='.$this->token.'&deleteImage2=1',
					'hint' => $this->l('Upload a secondary category logo from your computer.'),
				),

Once again, same code, different names. And, lastly, change this:

		$image = ImageManager::thumbnail(_PS_CAT_IMG_DIR_.'/'.$obj->id.'.jpg', $this->table.'_'.(int)$obj->id.'.'.$this->imageType, 350, $this->imageType, true);

		$this->fields_value = array(
			'image' => $image ? $image : false,
			'size' => $image ? filesize(_PS_CAT_IMG_DIR_.'/'.$obj->id.'.jpg') / 1000 : false
		);

Into this

		$image = ImageManager::thumbnail(_PS_CAT_IMG_DIR_.'/'.$obj->id.'.jpg', $this->table.'_'.(int)$obj->id.'.'.$this->imageType, 350, $this->imageType, true);
		$image2 = ImageManager::thumbnail(_PS_CAT_IMG_DIR_.'/'.$obj->id.'_second.jpg', $this->table.'_'.(int)$obj->id.'_second.'.$this->imageType, 350, $this->imageType, true);

		$this->fields_value = array(
			'image' => $image ? $image : false,
			'size' => $image ? filesize(_PS_CAT_IMG_DIR_.'/'.$obj->id.'.jpg') / 1000 : false,
			'image2' => $image2 ? $image2 : false,
			'size2' => $image2 ? filesize(_PS_CAT_IMG_DIR_.'/'.$obj->id.'_second.jpg') / 1000 : false
		);

Useless to mention we are doing the same, once more. We are done with renderForm, let’s deal with postImage() now!

Managing the new image upload and removal

Trying to fill in the new file input would not produce anything, at the moment. The whole upload magic is held through a method named postImage(), which we are going to override and extend. Thus, copy it from the original AdminCategoriesController, and paste it inside the override file. It should look like the following:


	protected function postImage($id)
	{
		$ret = parent::postImage($id);
		if (($id_category = (int)Tools::getValue('id_category')) &&
			isset($_FILES) && count($_FILES) && $_FILES['image']['name'] != null &&
			file_exists(_PS_CAT_IMG_DIR_.$id_category.'.jpg'))
		{
			$images_types = ImageType::getImagesTypes('categories');
			foreach ($images_types as $k => $image_type)
			{
				ImageManager::resize(
					_PS_CAT_IMG_DIR_.$id_category.'.jpg',
					_PS_CAT_IMG_DIR_.$id_category.'-'.stripslashes($image_type['name']).'.jpg',
					(int)$image_type['width'], (int)$image_type['height']
				);
			}
		}

		return $ret;
	}

Get rid of return $ret;, and add the following instead:



		$ret2 = $this->uploadImage($id.'_second', 'image2', $this->fieldImageSettings['dir'].'/');
		if (($id_category = (int)Tools::getValue('id_category')) &&
			isset($_FILES) && count($_FILES) && $_FILES['image2']['name'] != null &&
			file_exists(_PS_CAT_IMG_DIR_.$id_category.'_second.jpg'))
		{
			$images_types = ImageType::getImagesTypes('categories');
			foreach ($images_types as $k => $image_type)
			{
				ImageManager::resize(
					_PS_CAT_IMG_DIR_.$id_category.'_second.jpg',
					_PS_CAT_IMG_DIR_.$id_category.'_second-'.stripslashes($image_type['name']).'.jpg',
					(int)$image_type['width'], (int)$image_type['height']
				);
			}
		}

		return $ret && $ret2;

Explanation: first, we are grabbing some code directly from the adminController. We have to use a custom name for the image upload, which is the category id plus _second, using the field “image2″. The rest is simply a copy/paste of the above, with, again, changes in the name only. Lastly, we make sure that we return true only if both eventual uploads were successful.

We have a problem though. At the time being, Prestashop still thinks we only have one image for the category entity. Thus, when uploading any other image for it (excluding thumbs), it will erase the previous one. We can test it right away: upload a new image for the image2 field, you will notice the original category image will be erased. As we don’t want this, we have to grab imageUpload() directly from the AdminController.php file, and extend it. Locate the method, and paste it inside our override. In Prestashop 1.6.0.9, it looks like this:

	protected function uploadImage($id, $name, $dir, $ext = false, $width = null, $height = null)
	{
		if (isset($_FILES[$name]['tmp_name']) && !empty($_FILES[$name]['tmp_name']))
		{
			// Delete old image
			if (Validate::isLoadedObject($object = $this->loadObject()))
				$object->deleteImage();
			else
				return false;

			// Check image validity
			$max_size = isset($this->max_image_size) ? $this->max_image_size : 0;
			if ($error = ImageManager::validateUpload($_FILES[$name], Tools::getMaxUploadSize($max_size)))
				$this->errors[] = $error;

			$tmp_name = tempnam(_PS_TMP_IMG_DIR_, 'PS');
			if (!$tmp_name)
				return false;

			if (!move_uploaded_file($_FILES[$name]['tmp_name'], $tmp_name))
				return false;

			// Evaluate the memory required to resize the image: if it's too much, you can't resize it.
			if (!ImageManager::checkImageMemoryLimit($tmp_name))
				$this->errors[] = Tools::displayError('Due to memory limit restrictions, this image cannot be loaded. Please increase your memory_limit value via your server\'s configuration settings. ');

			// Copy new image
			if (empty($this->errors) && !ImageManager::resize($tmp_name, _PS_IMG_DIR_.$dir.$id.'.'.$this->imageType, (int)$width, (int)$height, ($ext ? $ext : $this->imageType)))
				$this->errors[] = Tools::displayError('An error occurred while uploading the image.');

			if (count($this->errors))
				return false;
			if ($this->afterImageUpload())
			{
				unlink($tmp_name);
				return true;
			}
			return false;
		}
		return true;
	}

Pay attention at this snippet:


			// Delete old image
			if (Validate::isLoadedObject($object = $this->loadObject()))
				$object->deleteImage();
			else
				return false;

It’s where the original image gets erased to make room for a new one. Change it to:


			// Delete old image
			if (Validate::isLoadedObject($object = $this->loadObject()))
			{
				if($name == 'image2')
					$object->deleteImage2();
				else $object->deleteImage();
			} else
				return false;

It won’t work yet as didn’t override the Category class and added the deleteImage2 method. Before leaving this file, we need to extend one, last function, postProcess(). Copy it again from the original AdminCategoriesController, it should be something like:


	public function postProcess()
	{
		if (!in_array($this->display, array('edit', 'add')))
			$this->multishop_context_group = false;
		if (Tools::isSubmit('forcedeleteImage') || (isset($_FILES['image']) && $_FILES['image']['size'] > 0) || Tools::getValue('deleteImage'))
		{
			$this->processForceDeleteImage();
			if (Tools::isSubmit('forcedeleteImage'))
				Tools::redirectAdmin(self::$currentIndex.'&token='.Tools::getAdminTokenLite('AdminCategories').'&conf=7');
		}

		return parent::postProcess();
	}

We need to take care of the secondary image deletion:


	public function postProcess()
	{
		if (!in_array($this->display, array('edit', 'add')))
			$this->multishop_context_group = false;
		if (Tools::isSubmit('forcedeleteImage') || (isset($_FILES['image']) && $_FILES['image']['size'] > 0) || Tools::getValue('deleteImage'))
		{
			$this->processForceDeleteImage();
			if (Tools::isSubmit('forcedeleteImage'))
				Tools::redirectAdmin(self::$currentIndex.'&token='.Tools::getAdminTokenLite('AdminCategories').'&conf=7');
		} else if(Tools::getValue('deleteImage2'))
		{
			$category = $this->loadObject(true);

			if (Validate::isLoadedObject($category))
				if($category->deleteImage2(true))
					Tools::redirectAdmin(self::$currentIndex.'&token='.Tools::getAdminTokenLite('AdminCategories').'&updatecategory&id_category='.$category->id.'&conf=7');
		}

		return parent::postProcess();
	}

And we are done with this file, let’s extend the Category class now.

Overriding the category class

Create a new file inside override/classes and name it Category.php (or use an existing override if you have it). First of all, we need to test it the back office functionality, so let’s add that deleteImage2() method:



Class Category extends CategoryCore
{


	public function deleteImage2($force_delete = false)
	{
		if (!$this->id)
			return false;
		
		if ($force_delete || !$this->hasMultishopEntries())
		{
			/* Deleting object images and thumbnails (cache) */
			if ($this->image_dir)
			{
				if (file_exists($this->image_dir.$this->id.'_second.'.$this->image_format)
					&& !unlink($this->image_dir.$this->id.'_second.'.$this->image_format))
					return false;
			}
			if (file_exists(_PS_TMP_IMG_DIR_.$this->def['table'].'_'.$this->id.'_second.'.$this->image_format)
				&& !unlink(_PS_TMP_IMG_DIR_.$this->def['table'].'_'.$this->id.'_second.'.$this->image_format))
				return false;
			if (file_exists(_PS_TMP_IMG_DIR_.$this->def['table'].'_mini_'.$this->id.'_second.'.$this->image_format)
				&& !unlink(_PS_TMP_IMG_DIR_.$this->def['table'].'_mini_'.$this->id.'_second.'.$this->image_format))
				return false;
	
			$types = ImageType::getImagesTypes();
			foreach ($types as $image_type)
				if (file_exists($this->image_dir.$this->id.'_second-'.stripslashes($image_type['name']).'.'.$this->image_format)
				&& !unlink($this->image_dir.$this->id.'_second-'.stripslashes($image_type['name']).'.'.$this->image_format))
					return false;
		}
		return true;
	}
}

To create it, I simply copied the original deleteImage from the objectModel, and modified the name of each entry to reflect our convention (_second).

Time to test the back office! Access the categories tab, then open up one of them and try adding an image. Then, delete this image to check if that works as well. Then again add one, and one immediately after to check it gets over-written. Lastly, make sure the original one isn’t erased during this process, and the new one is not affected by any operation on the first onee.

After checking this, it’s time too display the new thumb. Since we will use it in subcategories, we need to assign it at the time they are retrieved for the template display. This is held through the getSubcategories() method of the Category class. Thus, copy the original one and paste it inside our override:

	public function getSubCategories($id_lang, $active = true)
	{
		$sql_groups_where = '';
		$sql_groups_join = '';
		if (Group::isFeatureActive())
		{
			$sql_groups_join = 'LEFT JOIN `'._DB_PREFIX_.'category_group` cg ON (cg.`id_category` = c.`id_category`)';
			$groups = FrontController::getCurrentCustomerGroups();
			$sql_groups_where = 'AND cg.`id_group` '.(count($groups) ? 'IN ('.implode(',', $groups).')' : '='.(int)Group::getCurrent()->id);
		}

		$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
		SELECT c.*, cl.id_lang, cl.name, cl.description, cl.link_rewrite, cl.meta_title, cl.meta_keywords, cl.meta_description
		FROM `'._DB_PREFIX_.'category` c
		'.Shop::addSqlAssociation('category', 'c').'
		LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (c.`id_category` = cl.`id_category` AND `id_lang` = '.(int)$id_lang.' '.Shop::addSqlRestrictionOnLang('cl').')
		'.$sql_groups_join.'
		WHERE `id_parent` = '.(int)$this->id.'
		'.($active ? 'AND `active` = 1' : '').'
		'.$sql_groups_where.'
		GROUP BY c.`id_category`
		ORDER BY `level_depth` ASC, category_shop.`position` ASC');

		foreach ($result as &$row)
		{
			
			$row['legend'] = 'no picture';
		}
		return $result;
	}

Then, right after this:

$row['id_image'] = Tools::file_exists_cache(_PS_CAT_IMG_DIR_.$row['id_category'].'.jpg') ? (int)$row['id_category'] : Language::getIsoById($id_lang).'-default';

Add our new image definition

$row['id_image2'] = Tools::file_exists_cache(_PS_CAT_IMG_DIR_.$row['id_category'].'_second.jpg') ? (int)$row['id_category'] .'_second' : Language::getIsoById($id_lang).'-default';

And we are done with php!

Display the new image in the template

We are almost there. Open category.tpl, located in the theme’s folder. Locate:

{if $subcategory.id_image}
	<img class="replace-2x" src="{$link->getCatImageLink($subcategory.link_rewrite, $subcategory.id_image, 'medium_default')|escape:'html':'UTF-8'}" alt="" width="{$mediumSize.width}" height="{$mediumSize.height}" />
{else}

And change it to

{if $subcategory.id_image2}
	<img class="replace-2x" src="{$link->getCatImageLink($subcategory.link_rewrite, $subcategory.id_image2, 'medium_default')|escape:'html':'UTF-8'}" alt="" width="{$mediumSize.width}" height="{$mediumSize.height}" />
{else}

This will be enough if friendly urls are not enabled. But what if we are using rewritten URLs?. Sadly, we need to hardcode one, last modification in the .htaccess file. It is not exactly bulletproof, but it worked out well in all my tests. Therefore, open your .htaccess, and add the following at the very beginning, right BEFORE “# ~~start~~ Do not remove this comment, Prestashop will keep automatically the code outside this comment when .htaccess will be generated again”

<IfModule mod_rewrite.c>
RewriteRule ^c/([0-9]+)_second(\-[\.*_a-zA-Z0-9-]*)(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/c/$1_second$2$3.jpg [L]
</IfModule>

This will make sure our “_second” text in the image path won’t be treated as part of the category ID, thus making it impossible to retrieve the picture.

Conclusion

Despite being a bit cumbersome, adding a secondary image for categories (or actually any other entity such as manufacturers or suppliers) is indeed possible by using this technique. The final trick for rewritten-url environments is to make sure the proper regEx is added right at the beginning of the .htaccess file.

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

  • Franco Di Maria

    Hi,
    I use that code, but cant get to work that if NO Second Image was uploaded -> I want NO image to show.
    It always shows the default image of the shop.
    How can I achieve that?
    Thx for help,
    Franco

  • NemoPS

    Hello!
    Instead of the subcategories loop, you can use it outside with $category->id_image2

  • Boris

    Hello, first thank you for this tuto !
    I don’t know if you will read this comment… But what about to have this second image not for subcategory thumb ? If I want to show this image just in the category ? maybe beside the default one ? Which part I have to modify ?
    Thank you !

    • NemoPS

      Boris,
      You can just use the same code I put for the subcategories one in the main area, it will work the same way :)

  • ethernek

    Hello and thank you for this tutorial! I’ve trying to implement this to my prestashop (1.6.1.7) but as I can see some of the methods in Category class and in AdminCategoryController have changed since the writing of this article.

    Is there a chance to update it? And if not can you pinpoint the changes in the methods like postImage() and uploadImage() ?

    Thank you again!

  • http://www.juliencuenin.fr Julien Cuenin

    Hello, Thank you for your tutorial. I was able to add the new field to the category but i got a problem linked to the image type I want to use.

    I would like to use svg images and prestashop doesn’t allow this file extension. So, for this field, how can I make sure not to validate the file type please?

    Thank you for your help,

    Best regards,

    Julien

  • Dominik Wójcik

    Great tutorial – I was looking for something similar, therefore, I have a question: I’m trying to add an additional field in the controller, using the new database table – how to edit this field from the back office? Writing data to the new table takes place in the hooks.

    • NemoPS

      I have a tutorial for this: http://nemops.com/prestashop-products-new-tabs-fields/#.VzDk24T5jmg
      You can use admincategoriescontroller instead, or hook the module to displayBackOfficeCategory

      • Dominik Wójcik

        I decided to use displayBackOfficeCategory. Can I create a form using renderForm method and HelperForm ?

        • NemoPS

          Actually no. It’s inside a form already, you can create a template or in any case just return the input

        • Dominik Wójcik

          You’re right, thank you very much for your help ;) I have one last question: how to send variable to the file header.tpl – I would like there to decide whether to display the left column or not?

        • Dominik Wójcik

          public function hookHeader() – this method has helped me

  • NiNe NiNe

    this should be by default in PS ,its nice update

    but how you can deleted the thumbnail image in case you want use one image for both

  • Jesper Kochborg Andersen

    Thanks for the tutorial. Just tried downloading the files, and uploading to my overrides. I’m on 1.6.2.0
    The backoffice changes, and I see 2 pictures there… But I cant seem to display both images on my theme.

    There is something different in my code. In my Category.tpl I have this:


    {if $category->id_image}

    getCatImageLink($category->link_rewrite, $category->id_image, ‘Category_Header’)|escape:’html’}” alt=”{$category->name|escape:’htmlall':’UTF-8′}” title=”{$category->name|escape:’htmlall':’UTF-8′}” id=”categoryImage” />

    {/if}

    I tried duplicate it, and just replace with “id_image2″ – but that did not do the trick… can you help here ??

    Thanks

  • Maurizio Arzenton

    Great Tutorial… Thank you very much.

  • CelineMS

    Thank you SO MUCH for this tutorial! It helped me a lot.
    Have a nice day,
    Céline

  • Marin Paul

    Hi.

    Nice tut, but I cannot seem to make it work. I downloaded the files, extracted them like so:

    overridecontrollersadminAdminCategoriesController.php
    IN
    overridecontrollersadminAdminCategoriesController.php
    and
    overridecontrollersadmintemplatesAdminCategoriesController.php

    Copied it both places, but my bet is that the first option is the correct one. I`m pretty new at this:D

    Next file :
    overrideclassesCategory.php
    TO
    overrideclassesCategory.php

    and finally,

    category.tpl was uploaded in themesdefault-bootstrapcategory.tpl

    Can you please help me identifying what I did wrong?

    Also, the way I tested it was : Deleted a subcategory main img, tested that it wasn`t showing on site, then uploaded another image, hit save, back to the upload page, uploaded second image (the one I intend to use as a thumnail) . Doing so overrides the first upload. I tried selecting both pics and uploading, but that didn`t work.

    Hope you reply!…Nice tutorial!

  • CristiC

    Excellent article. Well done and works great.

    I do have one remark – when you are overriding one function in some cases is better if you don’t just copy the entire standard function. Instead call parrent::FUNCTION and then add your code. For example:

    public function getSubCategories($id_lang, $active = true)
    {
    $result = parent::getSubCategories($id_lang, $active);

    foreach ($result as &$row)
    {
    $row[‘id_image2′] = Tools::file_exists_cache(_PS_CAT_IMG_DIR_.$row[‘id_category’].’_second.jpg’) ? (int)$row[‘id_category’] .’_second’ : Language::getIsoById($id_lang).’-default';

    }

    return $result;
    }

    This way – you will make upgrades easier. You don’t have to copy again the entire function on a new version.

    Of course – this will not work in all cases. For example AdminCategoriesController – renderForm().

    Keep up the good work!

Store Top Sales

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