Module : Les clients ayant acheté cet article ont également acheté
- Par Magentix le 03/07/2010
- Difficulté : 3/4
Plutôt courant sur les sites e-commerce, cette extension Magento développé par Magentix permet de favoriser l'ajout de produits complémentaires au panier. Selon les produits acquis par les internautes, elle se chargera de récupérer et d'afficher l'ensemble produits achetés en complément du produit consulté.
L'objectif de cette extension est de proposer aux internautes des produits complémentaires générés automatiquement selon les produits acquis précédemment au sein d'un même panier. Cette technique de recoupement a pour avantage de fonctionner en totale autonomie, les produits apparaîtront au fur à mesure des achats sur le site (à condition que les paniers comportent plus d'un produit).
L'enjeu principale pour réaliser ce module est d'établir la bonne requête. L'extension est disponible en téléchargement sur la page de cet article (colonne de droite), voici quelques explications complémentaires.
Architecture du module
- app/code/local/Magentix/AlsoBought
- Block
- Bought.php
- etc
- config.xml
- Model
- Mysql4
- Bought.php
- Bought.php
- app/design/frontend/base/default
- layout
- alsobought.xml
- template
- alsobought
- bought.phtml
- app/etc/modules
- Magentix_AlsoBought.xml
Développement du module
Avant de se lancer dans le développement du module, il est primordiale de déterminer la requête à exécuter pour l'acquisition de l'identifiant des produits achetés en même temps que le produit consulté.
L'ensemble des produits achetés se situent dans la table sales_flat_order_item. Les conditions doivent être les suivantes :
- J'isole les commandes comportant le produit spécifique
- Je supprime les commandes dont le statut est annulé
- J'isole les produits contenus dans ces commandes
- Je supprime le produit spécifique des résultats
- Je regroupe les produits par identifiant afin d'éviter les doublons
Ce qui nous amène finalement à la requête suivante (où X représente l'identifiant du produit consulté) :
Requête
SELECT `s2`.`product_id` AS `id` FROM `sales_flat_order_item` AS `s1` INNER JOIN `sales_flat_order_item` AS `s2` ON s1.order_id = s2.order_id WHERE (s1.product_id = 'X') AND (s2.product_id != 'X') AND (s1.qty_canceled = 0) GROUP BY `s2`.`product_id`
Nous pouvons maintenant construire la requête à l'aide des méthodes proposés par Zend. Le méthode getProductsIds de la classe Magentix_AlsoBought_Model_Mysql4_Bought permet la construction puis l'exécution de la requête (l'adaptateur de lecture de la ressource est définie dans le fichier de configuration).
app/code/local/Magentix/AlsoBought/Model/Mysql4/Bought.php
<?php
class Magentix_AlsoBought_Model_Mysql4_Bought extends Mage_Core_Model_Mysql4_Abstract {
protected function _construct() {
$this->_init('alsobought/bought','id');
}
public function getProductsIds($product_id) {
$connection = $this->_getReadAdapter();
$select = $this->_getReadAdapter()->select()
->from(array('s1' => 'sales_flat_order_item'),array('id' => 's2.product_id'))
->join(array('s2' => 'sales_flat_order_item'),"s1.order_id = s2.order_id",array())
->where('s1.product_id = ?', $product_id)
->where('s2.product_id != ?', $product_id)
->where('s1.qty_canceled = ?', 0)
->group('s2.product_id');
if(!$connection->fetchRow($select)) return 0;
return $connection->fetchAll($select);
}
}
En paramètre se trouve l'identifiant du produit actuellement consulté. Depuis le modèle Magentix_AlsoBought_Model_Bought il est alors possible de filtrer une collection de produits selon les identifiants de produits récupérés grâce à la méthode getProductsIds que nous venons d'établir.
app/code/local/Magentix/AlsoBought/Model/Bought.php
<?php
class Magentix_AlsoBought_Model_Bought extends Mage_Core_Model_Abstract {
protected function _getResource() {
if (is_null($this->_resource)) {
$this->_resource = Mage::getResourceModel('alsobought/bought');
}
return $this->_resource;
}
public function getAlsoBoughtProductCollection($product_id) {
$ids = $this->_getResource()->getProductsIds($product_id);
$products = Mage::getResourceModel('catalog/product_collection')->addAttributeToFilter('entity_id',$ids);
return $products;
}
}
La suite du développement du module concerne la mise en place d'un bloc classique. Nous ne le détaillerons pas.
Affichage des produits
L'affichage des produits se gère depuis le fichier du template alsobought/bought.phtml. Il faudra prendre soin de le déplacer dans le template adéquat. Ce fichier est à peu de chose près identique au fichier du template de base utilisé pour l'affichage des produits en upsell (montée en gamme).
Le layout indique que le bloc est enfant du bloc product.info de la référence content. Il spécifie également le template à utiliser (alsobought/bought.phtml).
app/design/frontend/base/default/layout/alsobought.xml
<?xml version="1.0"?>
<layout version="0.1.0">
<catalog_product_view>
<reference name="content">
<block name="product.info">
<block type="alsobought/bought" name="product.info.alsobought" as="alsobought_products" template="alsobought/bought.phtml">
<action method="setColumnCount"><columns>4</columns></action>
<action method="setItemLimit"><type>alsobought</type><limit>4</limit></action>
</block>
</block>
</reference>
</catalog_product_view>
</layout>
De la même manière que pour les produits en upsell, on indique ici le nombre de colonne par ligne, et le nombre de produits total à afficher.
il ne reste plus qu'à éditer le fichier du template utilisé pour la fiche produit, et à ajouter le bloc alsobought à l'endroit souhaité :
app/design/frontend/base/default/template/catalog/product/view.phtml
<!-- ... -->
<div class="product-collateral">
<?php echo $this->getChildHtml('description') ?>
<?php echo $this->getChildHtml('additional') ?>
<?php echo $this->getChildHtml('upsell_products') ?>
<!-- Les clients ayant acheté cet article ont également acheté -->
<?php echo $this->getChildHtml('alsobought_products') ?>
<?php echo $this->getChildHtml('product_additional_data') ?>
</div>
<!-- ... -->
Notez que pour éviter l'exécution des requêtes à chaque visualisation d'une fiche produit j'ai opté pour une mise en cache de ces informations. La durée de vie définie dans le constructeur du bloc Magentix_AlsoBought_Block_Bought est de 3600 secondes.
Le module est compatible avec les versions 1.3.X et 1.4.X de Magento.
Quand flat catalog product et flat catalog category est vrai.
erreur
SELECT 1 AS `status`, `e`.`entity_id`, `e`.`type_id`, `e`.`attribute_set_id`, `e`.`price`, `e`.`special_price`, `e`.`special_from_date`, `e`.`special_to_date`, `e`.`name`, `e`.`description`, `e`.`short_description`, `e`.`price`, `e`.`special_price`, `e`.`special_from_date`, `e`.`special_to_date`, `e`.`image`, `e`.`small_image`, `e`.`thumbnail`, `e`.`news_from_date`, `e`.`tax_class_id`, `e`.`url_key`, `e`.`required_options`, `e`.`price_type`, `e`.`weight_type`, `e`.`price_view`, `e`.`shipment_type`, `e`.`image_label`, `e`.`small_image_label`, `e`.`thumbnail_label`, `e`.`batch_qty`, `e`.`homeselection`, `e`.`is_serial_number`, `e`.`warranty`, `e`.`enablesalecount`, `e`.`display_price_group_0` AS `_rule_price` FROM `catalog_product_flat_1` AS `e`
INNER JOIN `catalog_product_enabled_index` AS `enabled_index` ON enabled_index.product_id=e.entity_id AND enabled_index.store_id='1' AND enabled_index.visibility IN (2, 4) WHERE (e.entity_id = '0') ORDER BY `position` asc LIMIT 4
I tried when flat mode enabled, and no problem... The generated query looks fine. When you run this query in database what is the error returned ?
Petite question impertinente ;)
Je souhaite installer ce module sur un site en cours de développement...donc bien entendu aucune commande n'a été passée à l'heure actuelle ! Comment vérifier le fonctionnement et accessoirement travailler sur le gabarit ?
Merci
The problem is magento 1.3.1. I fixed the problem by updating Catalog and CatalogIndex in the core files to 1.3.2.4.
Great solution.
Thanks
Par exemple :
$products = Mage::getResourceModel('catalog/product_collection')->addAttributeToFilter('entity_id',array(1));
Cela récupérera le produit dont l'Identifiant (ID) est 1
Merci !
Par exemple:
->addStoreFilter($storeId) ;
Merci
Nicholas
L'extension posera problème sur un site dont les tables de la base comporte un préfixe. Je corrige çà prochainement.
$storeId = Mage::app()->getStore()->getId();
$this->_itemCollection->addAttributeToSort('position','asc')->addStoreFilter($storeId);
$storeId = $this->helper('core')->getStoreId();
This now works all the time and stops the URLS from mixing between stores.
il semble que cette extension soit buguée. Voici le message d'erreur généré :
exception 'Mage_Core_Exception' with message 'Invalid block type: ' in /chroot/home/glow/glow/html/app/Mage.php:550
Stack trace:
#0 /chroot/home/glow/glow/html/app/code/core/Mage/Core/Model/Layout.php(469): Mage::throwException('Invalid block t...')
#1 /chroot/home/glow/glow/html/app/code/core/Mage/Core/Model/Layout.php(411): Mage_Core_Model_Layout->_getBlockInstance('', Array)
#2 /chroot/home/glow/glow/html/app/code/core/Mage/Core/Model/Layout.php(446): Mage_Core_Model_Layout->createBlock('', 'product.info')
#3 /chroot/home/glow/glow/html/app/code/core/Mage/Core/Model/Layout.php(238): Mage_Core_Model_Layout->addBlock('', 'product.info')
#4 /chroot/home/glow/glow/html/app/code/core/Mage/Core/Model/Layout.php(204): Mage_Core_Model_Layout->_generateBlock(Object(Mage_Core_Model_Layout_Element), Object(Mage_Core_Model_Layout_Element))
#5 /chroot/home/glow/glow/html/app/code/core/Mage/Core/Model/Layout.php(209): Mage_Core_Model_Layout->generateBlocks(Object(Mage_Core_Model_Layout_Element))
#6 /chroot/home/glow/glow/html/app/code/core/Mage/Core/Controller/Varien/Action.php(343): Mage_Core_Model_Layout->generateBlocks()
#7 /chroot/home/glow/glow/html/app/code/core/Mage/Catalog/controllers/ProductController.php(115): Mage_Core_Controller_Varien_Action->generateLayoutBlocks()
#8 /chroot/home/glow/glow/html/app/code/core/Mage/Catalog/controllers/ProductController.php(149): Mage_Catalog_ProductController->_initProductLayout(Object(Mage_Catalog_Model_Product))
#9 /chroot/home/glow/glow/html/app/code/core/Mage/Core/Controller/Varien/Action.php(418): Mage_Catalog_ProductController->viewAction()
#10 /chroot/home/glow/glow/html/app/code/core/Mage/Core/Controller/Varien/Router/Standard.php(254): Mage_Core_Controller_Varien_Action->dispatch('view')
#11 /chroot/home/glow/glow/html/app/code/community/ArtsOnIT/OfflineMaintenance/Controller/Router/Standard.php(46): Mage_Core_Controller_Varien_Router_Standard->match(Object(Mage_Core_Controller_Request_Http))
#12 /chroot/home/glow/glow/html/app/code/core/Mage/Core/Controller/Varien/Front.php(177): ArtsOnIT_OfflineMaintenance_Controller_Router_Standard->match(Object(Mage_Core_Controller_Request_Http))
#13 /chroot/home/glow/glow/html/app/code/core/Mage/Core/Model/App.php(304): Mage_Core_Controller_Varien_Front->dispatch()
#14 /chroot/home/glow/glow/html/app/Mage.php(596): Mage_Core_Model_App->run(Array)
#15 /chroot/home/glow/glow/html/index.php(78): Mage::run('', 'website')
#16 {main}
Pas encore trouvé le moyen de fixer çà de mon côté.
Magento says this costs $1 but I don't see a buy button?
Does this app not come with an extension key?? I am not very technical and would not have the first idea of how to install it without one.
Please help!
regards
Leslee
Sorry to be a pain.
Merci
Leslee
apparemment la table sales_flat_order_item n'existe pas encore sur mon magento de développement : SQLSTATE[42S02]: Base table or view not found...
Il n'y a pas encore de commandes... faut il que je crée des commandes pour que ca marche ?
I would like to suggest a change, to make the choice of the related products a little better. Instead of having an "X" showing the relation, why dont we use a number, that is increased every time, the combination is bought? Then we can select the product-combination with the highest number an get the best results, right?
I dont know, how to do it, but if anybody knows a way, please let me know!
Thanks a lot,
Florian
I'm sorry fo my terrible French.
Thank you very much for this great extention!
I have installed it and it appears in the System/Configuration/Adanced. But I don't know where to set it to appear in the Products' Page. I have placed several orders with the different products, but the 'Also Bought' doesn't show anymore.
So could you please help me about it?
Thanks!
So i have changed the reference to "<reference name="product.info">" and added "<?php echo $this->getChildHtml('alsobought_products') ?>" to "template/catalog/product/view.phtml" and it works great :)
Nous avons installer l' extension dans notre shop en Allemagne mais nous savon pas comme un peut activer?
My french is not that good to read an IT instruction. Do you have anything in english for us you to activate the module?
Thanks and greetings from Germany
Any body an idea why it does not activate?
Greets
The only problem there is now is the Cache.
When changing store views, changing currencies. The cached information is retrieved and therefore no update on price or store is updated and so it produces some strange results.
protected function _construct() {
$product = Mage::registry('product');
$this->addData(array(
'cache_lifetime' => 900,
'cache_tags' => array(Mage_Catalog_Model_Product::CACHE_TAG),
'cache_key' => $this->getCacheKey()
));
}
public function getCacheKey()
{
return $this->getRequest()->getRequestUri().$this->getCacheCurrencyCode();
}
//retreive current currency code
public function getCacheCurrencyCode()
{
return Mage::app()->getStore()->getCurrentCurrencyCode();
}