Dans Prestashop 1.4, les attributs sont affichés par ordre alphabétique.

De base, il n’est donc pas possible de gérer l’ordre d’affichage des attributs.

Néanmoins, avec l’override il est désormais possible de gérer l’ordre.

Notre méthode consite à :

  • modifier le nom des attributs en ajoutant en préfixe, un numéro qui servira à indiquer l’ordre de l’attribut
  • modifier le fichier product.tpl de votre thème Prestashop
  • Overrider la classe Cart.php et modifier certains fichiers de module

1 Modifier le nom des attributs

Dans votre backoffice Prestashop, modifiez le nom de vos attributs en ajoutant un numéro suivi d’un point :

Prestashop : ordre des attributs

Prestashop : ordre des attributs

Attention, si vous utilisez déjà un point dans le nom de vos attributs, utilisez un autre caractère qui ne sera pas utilisé dans le nom de l’attribut.

Pour l’instant le nom des attributs s’affichent dans la boutique avec ce préfixe :

Prestashop : ordre attribut

Prestashop : ordre attribut

Nous devons donc supprimer ce préfixe.

2 Où sont affichés les attributs dans votre boutique Prestashop ?

Dans  Prestashop, les attributs sont affichés dans différentes pages :

  • la page produit
  • la page récapitulatif de la commande
  • la page historique de mes commandes (dans le compte client)
  • le bloc panier
  • la facture pdf
  • le bloc liste de cadeaux (si module wishlist est activé)
  • la page Mes listes (dans le compte client)

Selon ces pages, nous aurons besoin de modifier un fichier tpl, la class Cart.php ou un fichier dans un module.

3 Les modifications

3.1 Modification du fichier product.tpl

Ce fichier se trouve dans le dossier de votre thème Prestashop.

Avec smarty, nous devons trouver la position du point dans le nom de l’attribut, puis supprimer les caractères se trouvant avant ce point.

On remplace donc la ligne suivante :

{if isset($groups)}

<!-- attributes -->

<div id="attributes">{foreach from=$groups key=id\_attribute\_group item=group}

{if $group.attributes|@count}

<label for="group\_{$id\_attribute_group|intval}">{$group.name|escape:'htmlall':'UTF-8'} :</label>

{assign var="groupName" value="group\_$id\_attribute_group"}

<select name="{$groupName}" onchange="javascript:findCombination();{if $colors|@count > 0}$('#wrapResetImages').show('slow');{/if};"> {foreach from=$group.attributes key=id\_attribute item=group\_attribute}</select> <select name="{$groupName}" onchange="javascript:findCombination();{if $colors|@count > 0}$('#wrapResetImages').show('slow');{/if};"><option title="{$group\_attribute|escape:'htmlall':'UTF-8'}" selected="selected" value="{$id\_attribute|intval}"> {$group_attribute|escape:'htmlall':'UTF-8'}</option></select> <select name="{$groupName}" onchange="javascript:findCombination();{if $colors|@count > 0}$('#wrapResetImages').show('slow');{/if};"> {/foreach}</select>

{/if}

{/foreach}</div>

{/if}

Par :

{if isset($groups)}

<!-- attributes -->

<div id="attributes">{foreach from=$groups key=id\_attribute\_group item=group}

{if $group.attributes|@count}

<label for="group\_{$id\_attribute_group|intval}">{$group.name|escape:'htmlall':'UTF-8'} :</label>

{assign var="pos" value=$group_attribute|strpos:"."}

{if $pos!=false}

{$group_attribute|substr:($pos+1)|escape:'htmlall':'UTF-8'}

{else}

{$group_attribute|escape:'htmlall':'UTF-8'}

{/if}

<select name="{$groupName}" onchange="javascript:findCombination();{if $colors|@count > 0}$('#wrapResetImages').show('slow');{/if};"> {foreach from=$group.attributes key=id\_attribute item=group\_attribute}</select> <select name="{$groupName}" onchange="javascript:findCombination();{if $colors|@count > 0}$('#wrapResetImages').show('slow');{/if};"><option title="{$group\_attribute|escape:'htmlall':'UTF-8'}" selected="selected" value="{$id\_attribute|intval}"> {$group_attribute|escape:'htmlall':'UTF-8'}</option></select> <select name="{$groupName}" onchange="javascript:findCombination();{if $colors|@count > 0}$('#wrapResetImages').show('slow');{/if};"> {/foreach}</select>

{/if}

{/foreach}</div>

{/if}

3.2 Override de la classe Cart.php

Cette modification permettra d’afficher la liste des attributs sans le préfixe(ex : 01.,02., etc…).

On obtiendra le bon affichage pour les pages ou éléments suivants :

  • la page récapitulatif de la commande
  • la page historique de mes commandes (dans le compte client)
  • le bloc panier

Commençons par créer le fichier Cart.php pour l’override dans le dossier override/classes (à la racine de votre Prestashop) :

<!--?php class Cart extends CartCore { } ?-->

La fonction qui récupère le nom des attributs dans la classe Cart.php se nomme cacheSomeAttributesLists :

public static function cacheSomeAttributesLists($ipaList, $id_lang)

{

$paImplode = array();

foreach ($ipaList as $id\_product\_attribute)

if ((int)$id\_product\_attribute AND !array\_key\_exists($id\_product\_attribute.'-'.$id\_lang, self::$\_attributesLists))

{

$paImplode[] = (int)$id\_product\_attribute;

self::$\_attributesLists[(int)$id\_product\_attribute.'-'.$id\_lang] = array('attributes' => '', 'attributes_small' => '');

}

if (!count($paImplode))

return;

$result = Db::getInstance()->ExecuteS('

SELECT pac.\`id\_product\_attribute\`, agl.\`public\_name\` AS public\_group\_name, al.\`name\` AS attribute\_name

FROM \`'.\_DB\_PREFIX\_.'product\_attribute_combination\` pac

LEFT JOIN \`'.\_DB\_PREFIX\_.'attribute\` a ON a.\`id\_attribute\` = pac.\`id_attribute\`

LEFT JOIN \`'.\_DB\_PREFIX\_.'attribute\_group\` ag ON ag.\`id\_attribute\_group\` = a.\`id\_attribute\_group\`

LEFT JOIN \`'.\_DB\_PREFIX\_.'attribute\_lang\` al ON (a.\`id\_attribute\` = al.\`id\_attribute\` AND al.\`id\_lang\` = '.(int)$id\_lang.')

LEFT JOIN \`'.\_DB\_PREFIX\_.'attribute\_group\_lang\` agl ON (ag.\`id\_attribute\_group\` = agl.\`id\_attribute\_group\` AND agl.\`id\_lang\` = '.(int)$id_lang.')

WHERE pac.\`id\_product\_attribute\` IN ('.implode($paImplode, ',').')

ORDER BY agl.\`public_name\` ASC');

foreach ($result as $row)

{

self::$\_attributesLists\[$row['id\_product\_attribute'].'-'.$id\_lang\]\['attributes'\] .= $row['public\_group\_name'].' : '.$row['attribute_name'].'-big,';

self::$\_attributesLists\[$row['id\_product\_attribute'].'-'.$id\_lang\]\['attributes\_small'\] .= $row['attribute\_name'].'-small,';

}

foreach ($paImplode as $id\_product\_attribute)

{

self::$\_attributesLists\[$id\_product\_attribute.'-'.$id\_lang\]\['attributes'\] = rtrim(self::$\_attributesLists\[$id\_product\_attribute.'-'.$id\_lang\]\['attributes'\], ', ');

self::$\_attributesLists\[$id\_product\_attribute.'-'.$id\_lang\]\['attributes\_small'\] = rtrim(self::$\_attributesLists\[$id\_product\_attribute.'-'.$id\_lang\]\['attributes\_small'\], ', ');

}

}

La boucle à la ligne 472 récupère le nom de l’attribut : _$row[‘attributename’].

grâce aux fonctions php strpos et substr, nous allons supprimer le préfixe pour qu’il ne s’affiche pas.

Voici le fichier Cart.php (dans le dossier classe/override) complet :

<!--?php class Cart extends CartCore { protected static $\_attributesLists = array(); /*\* \* Return cart products \* \* @result array Products */ public function getProducts($refresh = false, $id\_product = false) { if (!$this\--->id)

return array();

// Product cache must be strictly compared to NULL, or else an empty cart will add dozens of queries

if ($this->_products !== NULL AND !$refresh)

return $this->_products;

$sql = '

SELECT cp.\`id\_product\_attribute\`, cp.\`id\_product\`, cu.\`id\_customization\`, cp.\`quantity\` AS cart\_quantity, cu.\`quantity\` AS customization\_quantity, pl.\`name\`,

pl.\`description\_short\`, pl.\`available\_now\`, pl.\`available\_later\`, p.\`id\_product\`, p.\`id\_category\_default\`, p.\`id\_supplier\`, p.\`id\_manufacturer\`, p.\`on\_sale\`, p.\`ecotax\`, p.\`additional\_shipping\_cost\`, p.\`available\_for_order\`,

p.\`quantity\`, p.\`price\`, p.\`weight\`, p.\`width\`, p.\`height\`, p.\`depth\`, p.\`out\_of\_stock\`, p.\`active\`, p.\`date\_add\`, p.\`date\_upd\`, IFNULL(pa.\`minimal\_quantity\`, p.\`minimal\_quantity\`) as minimal_quantity,

t.\`id\_tax\`, tl.\`name\` AS tax, t.\`rate\`, pa.\`price\` AS price\_attribute, pa.\`quantity\` AS quantity_attribute,

pa.\`ecotax\` AS ecotax\_attr, pl.\`link\_rewrite\`, cl.\`link\_rewrite\` AS category, CONCAT(cp.\`id\_product\`, cp.\`id\_product\_attribute\`) AS unique_id,

IF (IFNULL(pa.\`reference\`, '') = '', p.\`reference\`, pa.\`reference\`) AS reference,

IF (IFNULL(pa.\`supplier\_reference\`, '') = '', p.\`supplier\_reference\`, pa.\`supplier\_reference\`) AS supplier\_reference,

(p.\`weight\`+ pa.\`weight\`) weight_attribute,

IF (IFNULL(pa.\`ean13\`, '') = '', p.\`ean13\`, pa.\`ean13\`) AS ean13, IF (IFNULL(pa.\`upc\`, '') = '', p.\`upc\`, pa.\`upc\`) AS upc,

pai.\`id\_image\` pai\_id\_image, il.\`legend\` pai\_legend

FROM \`'.\_DB\_PREFIX\_.'cart\_product\` cp

LEFT JOIN \`'.\_DB\_PREFIX\_.'product\` p ON p.\`id\_product\` = cp.\`id_product\`

LEFT JOIN \`'.\_DB\_PREFIX\_.'product\_lang\` pl ON (p.\`id\_product\` = pl.\`id\_product\` AND pl.\`id\_lang\` = '.(int)$this->id\_lang.')

LEFT JOIN \`'.\_DB\_PREFIX\_.'product\_attribute\` pa ON (pa.\`id\_product\_attribute\` = cp.\`id\_product\_attribute\`)

LEFT JOIN \`'.\_DB\_PREFIX\_.'tax\_rule\` tr ON (p.\`id\_tax\_rules\_group\` = tr.\`id\_tax\_rules\_group\`

AND tr.\`id_country\` = '.(int)Country::getDefaultCountryId().'

AND tr.\`id_state\` = 0)

LEFT JOIN \`'.\_DB\_PREFIX\_.'tax\` t ON (t.\`id\_tax\` = tr.\`id_tax\`)

LEFT JOIN \`'.\_DB\_PREFIX\_.'tax\_lang\` tl ON (t.\`id\_tax\` = tl.\`id\_tax\` AND tl.\`id\_lang\` = '.(int)$this->id\_lang.')

LEFT JOIN \`'.\_DB\_PREFIX\_.'customization\` cu ON (cp.\`id\_product\` = cu.\`id\_product\` AND cp.\`id\_product\_attribute\` = cu.\`id\_product\_attribute\` AND cu.\`id\_cart\` = cp.\`id_cart\`)

LEFT JOIN \`'.\_DB\_PREFIX\_.'product\_attribute\_image\` pai ON (pai.\`id\_product\_attribute\` = pa.\`id\_product_attribute\`)

LEFT JOIN \`'.\_DB\_PREFIX\_.'image\_lang\` il ON (il.\`id\_image\` = pai.\`id\_image\` AND il.\`id\_lang\` = '.(int)$this->id\_lang.')

LEFT JOIN \`'.\_DB\_PREFIX\_.'category\_lang\` cl ON (p.\`id\_category\_default\` = cl.\`id\_category\` AND cl.\`id\_lang\` = '.(int)$this->id_lang.')

WHERE cp.\`id_cart\` = '.(int)$this->id.'

'.($id\_product ? ' AND cp.\`id\_product\` = '.(int)$id_product : '').'

AND p.\`id_product\` IS NOT NULL

GROUP BY unique_id

ORDER BY cp.date_add ASC';

$result = Db::getInstance()->ExecuteS($sql);

// Reset the cache before the following return, or else an empty cart will add dozens of queries

$productsIds = array();

$paIds = array();

foreach ($result as $row)

{

$productsIds[] = $row['id_product'];

$paIds[] = $row['id\_product\_attribute'];

}

// Thus you can avoid one query per product, because there will be only one query for all the products of the cart

Product::cacheProductsFeatures($productsIds);

self::cacheSomeAttributesLists($paIds, $this->id_lang);

$this->_products = array();

if (empty($result))

return array();

foreach ($result AS $row)

{

if (isset($row['ecotax\_attr']) AND $row['ecotax\_attr'] > 0)

$row['ecotax'] = (float)($row['ecotax_attr']);

$row['stock_quantity'] = (int)($row['quantity']);

// for compatibility with 1.2 themes

$row['quantity'] = (int)($row['cart_quantity']);

if (isset($row['id\_product\_attribute']) AND (int)$row['id\_product\_attribute'])

{

$row['weight'] = $row['weight_attribute'];

$row['stock\_quantity'] = $row['quantity\_attribute'];

}

if ($this->\_taxCalculationMethod == PS\_TAX_EXC)

{

$row['price'] = Product::getPriceStatic((int)$row['id\_product'], false, isset($row['id\_product\_attribute']) ? (int)($row['id\_product\_attribute']) : NULL, 2, NULL, false, true, (int)($row['cart\_quantity']), false, ((int)($this->id\_customer) ? (int)($this->id\_customer) : NULL), (int)($this->id), ((int)($this->{Configuration::get('PS\_TAX\_ADDRESS\_TYPE')}) ? (int)($this->{Configuration::get('PS\_TAX\_ADDRESS\_TYPE')}) : NULL), $specificPriceOutput); // Here taxes are computed only once the quantity has been applied to the product price

$row['price\_wt'] = Product::getPriceStatic((int)$row['id\_product'], true, isset($row['id\_product\_attribute']) ? (int)($row['id\_product\_attribute']) : NULL, 2, NULL, false, true, (int)($row['cart\_quantity']), false, ((int)($this->id\_customer) ? (int)($this->id\_customer) : NULL), (int)($this->id), ((int)($this->{Configuration::get('PS\_TAX\_ADDRESS\_TYPE')}) ? (int)($this->{Configuration::get('PS\_TAX\_ADDRESS_TYPE')}) : NULL));

$tax\_rate = Tax::getProductTaxRate((int)$row['id\_product'], (int)($this->{Configuration::get('PS\_TAX\_ADDRESS_TYPE')}));

$row['total\_wt'] = Tools::ps\_round($row['price'] \* (float)$row['cart\_quantity'] \* (1 + (float)($tax\_rate) / 100), 2);

$row['total'] = $row['price'] * (int)($row['cart_quantity']);

}

else

{

$row['price'] = Product::getPriceStatic((int)$row['id\_product'], false, (int)$row['id\_product\_attribute'], 6, NULL, false, true, $row['cart\_quantity'], false, ((int)($this->id\_customer) ? (int)($this->id\_customer) : NULL), (int)($this->id), ((int)($this->{Configuration::get('PS\_TAX\_ADDRESS\_TYPE')}) ? (int)($this->{Configuration::get('PS\_TAX\_ADDRESS\_TYPE')}) : NULL), $specificPriceOutput);

$row['price\_wt'] = Product::getPriceStatic((int)$row['id\_product'], true, (int)$row['id\_product\_attribute'], 2, NULL, false, true, $row['cart\_quantity'], false, ((int)($this->id\_customer) ? (int)($this->id\_customer) : NULL), (int)($this->id), ((int)($this->{Configuration::get('PS\_TAX\_ADDRESS\_TYPE')}) ? (int)($this->{Configuration::get('PS\_TAX\_ADDRESS_TYPE')}) : NULL));

/\* In case when you use QuantityDiscount, getPriceStatic() can be return more of 2 decimals \*/

$row['price\_wt'] = Tools::ps\_round($row['price_wt'], 2);

$row['total\_wt'] = $row['price\_wt'] * (int)($row['cart_quantity']);

$row['total'] = Tools::ps\_round($row['price'] * (int)($row['cart\_quantity']), 2);

}

if (!isset($row['pai\_id\_image']) OR $row['pai\_id\_image'] == 0)

{

$row2 = Db::getInstance()->getRow('

SELECT i.\`id_image\`, il.\`legend\`

FROM \`'.\_DB\_PREFIX_.'image\` i

LEFT JOIN \`'.\_DB\_PREFIX\_.'image\_lang\` il ON (i.\`id\_image\` = il.\`id\_image\` AND il.\`id\_lang\` = '.(int)$this->id\_lang.')

WHERE i.\`id\_product\` = '.(int)$row['id\_product'].' AND i.\`cover\` = 1');

if (!$row2)

$row2 = array('id_image' => false, 'legend' => false);

else

$row = array_merge($row, $row2);

}

else

{

$row['id\_image'] = $row['pai\_id_image'];

$row['legend'] = $row['pai_legend'];

}

$row['reduction_applies'] = ($specificPriceOutput AND (float)$specificPriceOutput['reduction']);

$row['id\_image'] = Product::defineProductImage($row, $this->id\_lang);

$row['allow\_oosp'] = Product::isAvailableWhenOutOfStock($row['out\_of_stock']);

$row['features'] = Product::getFeaturesStatic((int)$row['id_product']);

if (array\_key\_exists($row['id\_product\_attribute'].'-'.$this->id\_lang, self::$\_attributesLists))

$row = array\_merge($row, self::$\_attributesLists[$row['id\_product\_attribute'].'-'.$this->id_lang]);

$this->_products[] = $row;

}

return $this->_products;

}

public static function cacheSomeAttributesLists($ipaList, $id_lang)

{

$paImplode = array();

foreach ($ipaList as $id\_product\_attribute)

if ((int)$id\_product\_attribute AND !array\_key\_exists($id\_product\_attribute.'-'.$id\_lang, self::$\_attributesLists))

{

$paImplode[] = (int)$id\_product\_attribute;

self::$\_attributesLists[(int)$id\_product\_attribute.'-'.$id\_lang] = array('attributes' => '', 'attributes_small' => '');

}

if (!count($paImplode))

return;

$result = Db::getInstance()->ExecuteS('

SELECT pac.\`id\_product\_attribute\`, agl.\`public\_name\` AS public\_group\_name, al.\`name\` AS attribute\_name

FROM \`'.\_DB\_PREFIX\_.'product\_attribute_combination\` pac

LEFT JOIN \`'.\_DB\_PREFIX\_.'attribute\` a ON a.\`id\_attribute\` = pac.\`id_attribute\`

LEFT JOIN \`'.\_DB\_PREFIX\_.'attribute\_group\` ag ON ag.\`id\_attribute\_group\` = a.\`id\_attribute\_group\`

LEFT JOIN \`'.\_DB\_PREFIX\_.'attribute\_lang\` al ON (a.\`id\_attribute\` = al.\`id\_attribute\` AND al.\`id\_lang\` = '.(int)$id\_lang.')

LEFT JOIN \`'.\_DB\_PREFIX\_.'attribute\_group\_lang\` agl ON (ag.\`id\_attribute\_group\` = agl.\`id\_attribute\_group\` AND agl.\`id\_lang\` = '.(int)$id_lang.')

WHERE pac.\`id\_product\_attribute\` IN ('.implode($paImplode, ',').')

ORDER BY agl.\`public_name\` ASC');

foreach ($result as $row)

{

$att\_name\_prefix=$row['attribute_name'];

$pos=strpos($att\_name\_prefix,".");

if($pos != false)

{

$att\_name\_prefix=substr($att\_name\_prefix,($pos+1));

}

self::$\_attributesLists\[$row['id\_product\_attribute'].'-'.$id\_lang\]\['attributes'\] .= $row['public\_group\_name'].' : '.$att\_name\_prefix.',';

self::$\_attributesLists\[$row['id\_product\_attribute'].'-'.$id\_lang\]\['attributes\_small'\] .= $att\_name_prefix.',';

}

foreach ($paImplode as $id\_product\_attribute)

{

self::$\_attributesLists\[$id\_product\_attribute.'-'.$id\_lang\]\['attributes'\] = rtrim(self::$\_attributesLists\[$id\_product\_attribute.'-'.$id\_lang\]\['attributes'\], ', ');

self::$\_attributesLists\[$id\_product\_attribute.'-'.$id\_lang\]\['attributes\_small'\] = rtrim(self::$\_attributesLists\[$id\_product\_attribute.'-'.$id\_lang\]\['attributes\_small'\], ', ');

}

}

}

?>

3.3 Modification du module Wislist

Nous utilisons la même technique que pour l’override de Cart.php.

Le fichier à modifier est Wishlist.php (ligne 280) dans le dossier modules/wishlist :

$products\[$i\]\['attributes_small'\] = '';

if ($result)

foreach ($result AS $k => $row){

$att\_name\_prefix=$row['attribute_name'];

$pos=strpos($att\_name\_prefix,".");

if($pos !== false)

{

$att\_name\_prefix=substr($att\_name\_prefix,($pos+1));

}

$products\[$i\]\['attributes\_small'\] .= $att\_name_prefix.', ';

}

$products\[$i\]\['attributes\_small'\] = rtrim($products\[$i\]\['attributes\_small'\], ', ');

3.4 Modification du module Mailalerts

Le fichier à modifier est mailalerts.php (ligne 533) dans le dossier modules/mailalerts :

$products\[$i\]\['attributes_small'\] = '';

if ($result)

foreach ($result AS $k => $row){

$att\_name\_prefix=$row['attribute_name'];

$pos=strpos($att\_name\_prefix,".");

if($pos !== false)

{

$att\_name\_prefix=substr($att\_name\_prefix,($pos+1));

}

$products\[$i\]\['attributes\_small'\] .= $att\_name_prefix.', ';

}

$products\[$i\]\['attributes\_small'\] = rtrim($products\[$i\]\['attributes\_small'\], ', ');

Pour résumer, en modifiant 4 fichiers (20 min de travail), vous pouvez maintenant gérer l’ordre d’affichage des attributs sans  module !!