Tuesday, January 29, 2013

Share cart between websites

I saw that this is a general issue among Magneto users/developers.
Here is how I was able to keep the cart between websites.
The solution is not fully tested yet but for me it seams to work.
[Update]
Warning: I seams that this solution doesn't work with multiple currencies installed. See comments from MichaƂ Burda
[/Update]
If someone tries it please let me know the result and eventual bugs that appear.
Preconditions:
  • 'Use SID on Frontend' must be st to 'Yes' (System->Configuration->Web->Session Validation Settings)
Approach:
Magneto already allows you to keep the quote (cart) between store views from the same website.
I tried to manipulate this feature into letting me keep the cart between all websites (store views).
How is the quote kept in $_SESSION?
Magento keeps the quote id in the $_SESSION like this:
$_SESSION['quote_id_5'] = 34;
In the code above, 34 represents the quote id and 5 represents the website it (not store view id).
So it's basically like this:
$_SESSION['quote_id_{WEBSITE_ID}'] = {QUOTE_ID};
This means that the quote is different for each website.
Now for the actual code:
I've created a new extension called Easylife_Sales.
The extension has a fail-safe, in case it doesn't work as expected, it's behavior can be disabled from the configuration area. but more on this later.
Extension files:
app/etc/modules/Easylife_Sales.xml - declaration file
<?xml version="1.0"?>
<config>
    <modules>
        <Easylife_Sales>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
             <Mage_Sales /><!-- so it's loaded after Mage_Sales -->
             <Mage_Checkout /><!-- so it's loaded after Mage_Checkout -->
            </depends>
        </Easylife_Sales>
    </modules>
</config>
app/code/local/Easylife/Sales/etc/config.xml - configuration file
<?xml version="1.0"?>
<config>
 <modules>
  <Easylife_Sales>
   <version>0.0.1</version>
  </Easylife_Sales>
 </modules>
 <global>
  <models>
   <sales>
    <rewrite>
     <observer>Easylife_Sales_Model_Observer</observer>
     <quote>Easylife_Sales_Model_Quote</quote>
    </rewrite>
   </sales>
   <checkout>
    <rewrite>
     <session>Easylife_Sales_Model_Checkout_Session</session>
    </rewrite>
   </checkout>
  </models>
  <helpers>
   <sales>
    <rewrite>
     <data>Easylife_Sales_Helper_Data</data>
    </rewrite>
   </sales>
  </helpers>
 </global>
 <frontend>
  <events><!-- this section is not mandatory, explanations later -->
   <sales_quote_collect_totals_before>
    <observers>
     <easylife_sales>
      <class>sales/observer</class>
      <method>checkProductAvailability</method>
     </easylife_sales>
    </observers>
   </sales_quote_collect_totals_before>
  </events>
 </frontend>
 <default>
  <checkout>
   <options>
    <persistent_quote>1</persistent_quote><!-- this is for the fail-safe -->
   </options>
  </checkout>
 </default>
</config>
app/code/local/Easylife/Sales/Helper/Data.php - override the default sales helper to add the fail-save method
<?php 
class Easylife_Sales_Helper_Data extends Mage_Sales_Helper_Data{
    public function getIsQuotePersistent(){
  return Mage::getStoreConfigFlag('checkout/options/persistent_quote');
 }
}
app/code/local/Easylife/Sales/Model/Checkout/Session.php - override the default checkout session in order to change the session key for the quote
<?php 
class Easylife_Sales_Model_Checkout_Session extends Mage_Checkout_Model_Session{
 protected function _getQuoteIdKey()
    {
     if (Mage::helper('sales')->getIsQuotePersistent()){//if behavior is not disabled
         return 'quote_id';
     }
     return parent::_getQuoteIdKey();
    }
}
app/code/local/Easylife/Sales/Model/Quote.php - override the quote model to share between all websites
<?php 
class Easylife_Sales_Model_Quote extends Mage_Sales_Model_Quote{
 public function getSharedStoreIds(){
  if (Mage::helper('sales')->getIsQuotePersistent()){//if behavior is not diasabled
   $ids = Mage::getModel('core/store')->getCollection()->getAllIds();
   unset($ids[0]);//remove admin just in case
   return $ids;
  }
  return parent::getSharedStoreIds();
 }
}
app/code/local/Easylife/Sales/etc/system.xml - this allows you do disable the functionality in case something is wrong.
<?xml version="1.0"?>
<config>
 <sections>
  <checkout>
   <groups>
    <options>
     <fields>
      <persistent_quote translate="label" module="sales">
       <label>Keep cart between websites</label>
       <frontend_type>select</frontend_type>
       <source_model>adminhtml/system_config_source_yesno</source_model>
       <sort_order>100</sort_order>
       <show_in_default>1</show_in_default>
       <show_in_website>0</show_in_website>
       <show_in_store>0</show_in_store>
      </persistent_quote>
     </fields>
    </options>
   </groups>
  </checkout>
 </sections>
</config>
We are almost done. At this point the following happens. If you have a product in cart, you change the website and the product is not available in the new site the product is still in the cart. If you want this behavior then there is no problem. All you need to do is to remove these lines from config.xml
<observer>Easylife_Sales_Model_Observer</observer>
and
<events><!-- this section is not mandatory, explanations later -->
 <sales_quote_collect_totals_before>
  <observers>
   <easylife_sales>
    <class>sales/observer</class>
    <method>checkProductAvailability</method>
   </easylife_sales>
  </observers>
 </sales_quote_collect_totals_before>
</events>
In my case I wanted to remove from cart (permanently) the products that are not available in that website. if you want this behavior add the following file: app/code/local/Easylife/Sales/Model/Observer.php - this will remove from cart the products that are not valid on the current store.
<?php 
class Easylife_Sales_Model_Observer extends Mage_Sales_Model_Observer{
        public function checkProductAvailability($observer){
  if (!Mage::helper('sales')->getIsQuotePersistent()){
   return $this;
  }
  $quote = $observer->getEvent()->getQuote();
  $currentId = Mage::app()->getWebsite()->getId();
  
  $messages = array();
  
  foreach ($quote->getAllItems() as $item){   
   $product = $item->getProduct();
   if (!in_array($currentId, $product->getWebsiteIds())){
    $quote->removeItem($item->getId());
    $messages[] = Mage::helper('catalog')->__('Product %s is not available on website %s', $item->getName(), Mage::app()->getWebsite()->getName());
   }
  }
  foreach ($messages as $message){
   Mage::getSingleton('checkout/session')->addError($message);
  }
  return $this;
 }
}
Well that's about it. Enjoy and let me know how it turns out.

Marius.