Magento

Tout sur la solution e-commerce Magento

Https + Magento Cache + Internet Explorer = No image

Magento uses by default an advanced cache system in order to display templates content faster. But default configuration of this cache can sometime creates some issues.

Recently, we had some images that were not displayed in the top menu when we switch from an http page to a https page using Internet Explorer browser. Images in this menu still have a non secured (http) URI. Internet Explorer is not pleased with such content. By default, this browser does not display unsecured content on a secured page. We have this security warning displayed when some image have http URI on a https page:

Internet Explorer Security Warning

Block displaying top menu was using cache system but this cache was not regenerated when we switch from a http to a https page. That's why images were still having http URI.

For fixing this problem, we must rewrite getCacheKey method from Mage_Catalog_Block_Navigation class. A new parameter telling if page use http or https should be included in cache key. Cache will then be different for this 2 types of pages. This parameter is added with getSkinUrl method

Default code of getCacheKey

public function getCacheKey()
{
    return 'CATALOG_NAVIGATION_' . Mage::app()->getStore()->getId()
        . '_' . Mage::getDesign()->getPackageName()
        . '_' . Mage::getDesign()->getTheme('template')
        . '_' . Mage::getSingleton('customer/session')->getCustomerGroupId()
        . '_' . md5($this->getTemplate() . $this->getCurrenCategoryKey());
}

is then replaced by following code

public function getCacheKey()
{
    return 'CATALOG_NAVIGATION_' . Mage::app()->getStore()->getId()
        . '_' . Mage::getDesign()->getPackageName()
        . '_' . Mage::getDesign()->getTheme('template')
        . '_' . Mage::getSingleton('customer/session')->getCustomerGroupId()
        . '_' . md5($this->getTemplate() . $this->getCurrenCategoryKey())
        . '_' . md5($this->getSkinUrl()); /*** FIX IN THIS LINE ***/
}

This is it. You can now use peacefully https on Internet Explorer.

For more details about Magento cache system you can read this article.

Increase your sales! - Promotions rules in your Magento

Magento system, offers users an excellent tool for creating rules of special prices for multiply products at the same time. We have 2 types of rules: the first refers to a catalog of products, while the other to the contents of customers shopping cart. Let's look little bit closer at this first one.

In this example, we'll create a rule of reducing 50% of the price of each product in a specific category in our catalog.

Go to Promotions -> Catalog Price Rules -> Add New Rule

At the beginning we give the name of the rule, then we mark stores to take effect in (obviously, if we have more than 1) and select group of clients. Remember to include the start date and end date of promotion period, You can also enter a From/To Time to be more specific.

On the Conditions Tab, set a condition to define the category of products: if the product is equal category id 44 (Sacs enfants) then...

..then perform the action: apply a percentage discount of the original price by 50 (50%).
Accept a new rule by click on "Save & Apply"

From now on, prices of all products from category 'Sacs enfants' ( category_id = 44) will be reduced by 50%. Products will display the original price (crossed out) and promotion one (as a rule).

Please note, prices usually refer to the standard price of the product. So if the product is set to individual special price it will be superior, depending on how we set the condition in price rule.

Enjoy your sales!:)

Magento dropdown attributes

Let's say you want to create a new attribute for a product, e.g. 'color'.
There should be a set of possible values for this attribute. So what you do is create
an attribute of 'Dropdown' type. You set all its properties, give it a title and create its options, e.g. 'black', 'white', 'red' and 'yellow'.
But there is still one surprise that you may have. When you try to obtain the color simply by:

$product->getColor()

all you get is a numeric value corresponding to an option value in the dropdown.
But you probably would like to get a color name.
Fortunately you don't need to construct database queries, as Magento already thought about it.
So to get the name of the color selected for a product you need to use a method:
$product->getAttributeText('color');

where 'color' is the text that you have set as attribute code. Remember that you cannot
change attribute code nor its type after you create it.

Setting-up Ruled-Based Product Relations for automatic cross-sell with Magento Enterprise Edition 1.7

Managing cross-sell relations can take a long time with Magento Community Edition if you have many products in you catalog.

Product Relations with Magento Community Edition

With Magento Community Edition, you used to have to create relations between each product (cross-sell, up-sell and related products), product after product. Making this automatic required to develop a specific module or to import links from another system.

Setting-up Ruled-Based Product Relations for automatic cross-sell with Magento Enterprise Edition 1.7

Today Magento Enterprise Edition 1.7 allows you to create Ruled-Based Product Relations for automatic cross-sell links between your products.

Setting-up Ruled-Based Product Relations for automatic cross-sell with Magento Enterprise Edition 1.7

You can create several rules, define a priority so that you know which rule will be used if several ones apply.

Rules can be permanant or apply from a specific date to another.

These rules handle the 3 basic relations: cross-sell, up-sell and related products.

Setting-up Ruled-Based Product Relations for automatic cross-sell with Magento Enterprise Edition 1.7

Next you define if the rule applies to every products or just to some of them. What you can see here is the standard Magento attribute filter Rule engine.

Setting-up Ruled-Based Product Relations for automatic cross-sell with Magento Enterprise Edition 1.7

Then you define which product will be displayed. This Rule engine is slightly different since it allows to select an attribute value that will be equal to the main product.

At last you just have to save the rule so that it is applied and that linked products are displayed on the right blocks (cross-sell, up-sell and related products).

Bargento 4 announced

Bargento

Bargento 4 was announced. The event will take place Monday 22nd March Friday 28th March 2010 at Paris.

Program and Bargento place will be revealed soon.

Official announce in bargento.fr site (in french)

Magento: reload color images for configurable product

Let's talk about one situation. We are in shop made in Magento.

We are on configurable product page. One of configurable attributes is color. Wouldn't it be good idea, after color is chosen show only images of this color? It can be... and it's not hard to achieve.

First we must do some preparations:

Create attribute "image_storing" type boolean and add it to your attribute sets. Can be limited only to simple products.

For each color of superproduct (configurable+simples) choose one simple product and set value to yes, also to this product upload images of this color.

Now create new module called "imageswitch" (or whatever you want, just remember to change it in the following code).

In it you must have a controller looking like this:

class Baobaz_Imageswitch_IndexController extends Mage_Core_Controller_Front_Action
{
    public function indexAction()
    {
        $prod_id  = (int) $this->getRequest()->getParam('prod_id');
        $color_value  = (int) $this->getRequest()->getParam('color_id');
        $product=Mage::getModel('catalog/product')->load($prod_id);
        if($color_value) {
            $allProducts = $product->getTypeInstance(true)->getUsedProducts(null, $product);      
            foreach ($allProducts as $prod) {
                if ($prod->getData('image_storing') && $prod->getColor()==$color_value) { // && $prod->isSaleable()
                    break;
                }
            }          
            $prod_full=Mage::getModel('catalog/product')->load($prod->getId());
            Mage::register('product', $prod_full);
        }
        else {
            Mage::register('product', $product);
        }
        $this->loadLayout();    
        $this->renderLayout();
    }
}

This controller will reload the whole media block, but for that we also need proper layout xml file

imageswitch.xml

<?xml version="1.0"?>
<layout version="0.1.0">
    <imageswitch_index_index>
        <reference name="root">
            <action method="setTemplate"><template>page/empty.phtml</template></action>
        </reference>
        <reference name="content">
            <block type="catalog/product_view_media" name="product.info.media" as="media" template="catalog/product/view/media.phtml" />
        </reference>
    </imageswitch_index_index>
</layout>

and a layout html file - page/empty.phtml

<!-- start content -->
    <?php echo $this->getChildHtml('content') ?>
<!-- end content -->

Then we need to add some observer to catalog/product/view.phtml so we can reload it when the value of color is changed (attribute76 is the id of select for attribute color, it's default id if we use standard Magento attribute, if changed, may need adjusting)

<script>
function runajax() {
    product_id=$('product_id').value
    color_id=$('attribute76').value;
   
    new Ajax.Updater('product_media_content', '<?php echo Mage::getBaseUrl(); ?>imageswitch/index/index/prod_id/'+product_id+'/color_id/'+color_id, { method: 'get', evalScripts: true });
}

if ($('attribute76')) {
    Event.observe('attribute76', 'change', runajax);  
}
</script>

and add in the same file id of block to be added by changing :

<div class="product-img-box">
    <?php echo $this->getChildHtml('media') ?>
</div>

<div class="product-img-box" id="product_media_content">
    <?php echo $this->getChildHtml('media') ?>
</div>

and if all is done properly, now you should be able to see the effect. enjoy!

Setting up Magento with multiple websites or stores

There are many tutorials how to set Magento to work with multiple stores and make different domains point at each store. Since release of Magento CE 1.4.0.0-beta1 and Magento EE 1.6.0.0 it is even more easy to do.

Magento evolves

Solutions used in previous versions required developer to modify index.php file to handle different domains pointing at different stores. New index php contains following code:

$mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : '';
$mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store';

Mage::run($mageRunCode, $mageRunType);

So it checks two environmental variables and use them to start Magento runing. What does it give you? You can set now which store/website is supposed to be running under selected domain directly in virtual host definition or even htaccess.

VirtualHost solution

To benefit from this little piece of code it is enough for you to add following lines within your VirtualHost definition:

SetEnv MAGE_RUN_CODE "base" # put here your website or store code
SetEnv MAGE_RUN_TYPE "website" # put here 'website' or 'store'

.htaccess solution

If you have no access to virtual host definitions, you can still try to use .htaccess for that, putting within following lines:

SetEnvIf Host .*yourhost.* MAGE_RUN_CODE="base";
SetEnvIf Host .*yourhost.* MAGE_RUN_TYPE="website";

Where .*yourhost.* is an regex expression matching the domain for which you want to set environmental variable.

So now you are capable of setting up your Magento multiple stores website without messing up with the core. Good luck.

Customizing Magento Dataflow - import of custom data.

The flexibility of Magento Dataflow module lies in fact you can easily create your own adapters, parsers, mappers and apply them to your specific dataflow needs.

The basic case you may wonder how to do, is import of data for your custom module. Let's do this by example. Imagine you need to display on you e-shop list of stores, you have created custom module, table in database and datamodel part, all you need now is to populate this table with data you have within csv file.

Read the file

First you have to read the file. As you already know (if you read Magento Dataflow - Default Adapters [Part 2]) you can use dataflow/convert_adapter_io adapter for this.

<action type="dataflow/convert_adapter_io" method="load">
    <var name="type">file</var>
    <var name="path">var/import</var>
    <var name="filename"><![CDATA[stores.csv]]></var>
    <var name="format"><![CDATA[csv]]></var>
</action>

Parse the file content

Now that you have read the file content, you should parse it using dataflow/convert_parser_csv.

<action type="dataflow/convert_parser_csv" method="parse">
    <var name="delimiter"><![CDATA[,]]></var>
    <var name="enclose"><![CDATA["]]></var>
    <var name="fieldnames">true</var>
    <var name="store"><![CDATA[0]]></var>
    <var name="number_of_records">1</var>
    <var name="decimal_separator"><![CDATA[.]]></var>
</action>

Process rows of data

Now the custom part of this process. Within your custom module you have to create custom adapter that will create row in database for each processed row of parsed file. Within your module root directory create file ./Model/Convert/Adapter/Store.php of this content:

class Baobaz_Offer_Model_Convert_Adapter_Offer
    extends Mage_Dataflow_Model_Convert_Adapter_Abstract
{
    protected $_storeModel;

    public function load() {
      // you have to create this method, enforced by Mage_Dataflow_Model_Convert_Adapter_Interface
    }

    public function save() {
      // you have to create this method, enforced by Mage_Dataflow_Model_Convert_Adapter_Interface      
    }

    public function getStoreModel()
    {
        if (is_null($this->_storeModel)) {
            $storeModel = Mage::getModel('baobaz_store/store');
            $this->_storeModel = Mage::objects()->save($storeModel);
        }
        return Mage::objects()->load($this->_storeModel);
    }

    public function saveRow(array $importData)
    {
      $store = $this->getStoreModel();

      if (empty($importData['code'])) {
          $message = Mage::helper('catalog')->__('Skip import row, required field "%s" not defined', 'code');
          Mage::throwException($message);
      }
      else
      {
        $store->load($importData['code'],'code');
      }

      $store->setCode($importData['code']);
      $store->setName($importData['name']);

      $store->save();

      return true;

    }
}

Now when you have this file created you can modify a little bit the declaration of parser adding adapter and method variables: 

<action type="dataflow/convert_parser_csv" method="parse">
    <var name="delimiter"><![CDATA[,]]></var>
    <var name="enclose"><![CDATA["]]></var>
    <var name="fieldnames">true</var>
    <var name="store"><![CDATA[0]]></var>
    <var name="number_of_records">1</var>
    <var name="decimal_separator"><![CDATA[.]]></var>
    <var name="adapter">baobaz_store/convert_adapter_store</var>
    <var name="method">saveRow</var>
</action>

Having this done you should have your xml definition of custom dataflow profile looking like that:

<action type="dataflow/convert_adapter_io" method="load">
    <var name="type">file</var>
    <var name="path">var/import</var>
    <var name="filename"><![CDATA[stores.csv]]></var>
    <var name="format"><![CDATA[csv]]></var>
</action>
<action type="dataflow/convert_parser_csv" method="parse">
    <var name="delimiter"><![CDATA[,]]></var>
    <var name="enclose"><![CDATA["]]></var>
    <var name="fieldnames">true</var>
    <var name="store"><![CDATA[0]]></var>
    <var name="number_of_records">1</var>
    <var name="decimal_separator"><![CDATA[.]]></var>
    <var name="adapter">baobaz_store/convert_adapter_store</var>
    <var name="method">saveRow</var>
</action>

You can now enjoy your custom dataflow

Magento Dataflow - standard parsers and mapping values [part 4]

As promised in Magento Dataflow - Default Adapters [Part 2] today I will write about standard parsers in Magento DataFlow module and mapping values with mappers.

  1. Parser definition

    Parsers are responsible for transforming data from. Parser's interface Mage_Dataflow_Model_Convert_Parser_Interface defines two methods required in each parser: parse() and unparse(). Definition of parser within profile's xml can be as simple as:

    <action type="dataflow/convert_parser_serialize" method="parse" />

    Similar to adapter we define action tag with two attributes: type, which tells which class we want to use and this class's method we want to call. We can also call parser passing variables within action tag body as you will see below.

  2. Standard parsers

    Magento DataFlow includes few standard parsers which you can find in app/code/core/Dataflow/Model/Convert/Parser.

    The simplest of standard parsers is dataflow/convert_parser_serialize (Mage_Dataflow_Model_Convert_Parser_Serialize) which doesn't require any variables passed. It requires though that any of previous actions set data within profile's container. Method parse() unserialize data stored within profile's container and replace it with the result. Method unparse() do the opposite, so it serializes data stored within profile's container and replace it with the result.

    One of most often used standard parsers is dataflow/convert_parser_csv which allows transforming from (with method parse()) or to (with method unparse()) CSV file. Example of definition:

    <action type="dataflow/convert_parser_csv" method="parse">
        <var name="delimiter"><![CDATA[,]]></var>
        <var name="enclose"><![CDATA["]]></var>
        <var name="fieldnames">true</var>
        <var name="store"><![CDATA[0]]></var>
        <var name="decimal_separator"><![CDATA[.]]></var>
        <var name="adapter">catalog/convert_adapter_product</var>
        <var name="method">parse</var>
    </action>

    This parser requires that you call some io adapter prior to its execution (using for example dataflow/convert_adapter_io to read some csv file) if you want to call method parse. If you want to store data into CSV file you have to do both - call any action that will set data within profile's container prior to parser execution and call io adapter after parser execution to store data within file.

    Following variables will allow you to customize csv file parsing:

    • delimiter - defines delimiter used in csv file; defaults to comma (,) character
    • enclose - defines what character is used to enclose data values; defaults to empty character
    • escape - defines escape character for csv file; defaults to \\
    • decimal_separator - defines decimal separator sign
    • fieldnames - if set to true, it is assumed first row of csv file contains field names; if set to false map variable is used
    • map - defines fieldnames for files where first row doesn't contain fieldnames; to see how to define a map take a look at section of this article related to mapping values
    • adapter - tells which adapters method should be called on each row
    • method - tells which method of adapter should be called on each row; defaults to saveRow

    All variables defined within parser's action body are passed to the defined adapter, so if you need to pass something to it, you can simply set required variable within parser's action body.

    Last of standard parsers included within DataFlow module is dataflow/convert_parser_xml_excel (Mage_Dataflow_Model_Convert_Parser_Xml_Excel), which converts data from and to Excel xml file. Example of definition:

    <action type="dataflow/convert_parser_xml_excel" method="unparse">
        <var name="single_sheet"><![CDATA[products]]></var>
        <var name="fieldnames">true</var>
    </action>

    Use requirements are the same as for dataflow/convert_parser_csv.

    Following variables will allow you to customize csv file parsing:

    • fieldnames - if set to true, it is assumed first row of csv file contains field names; if set to false map variable is used
    • map - defines fieldnames for files where first row doesn't contain fieldnames
    • single_sheet - tells if parsed should be one sheet or all; should contain name of the sheet to be parsed
    • adapter - tells which adapters method should be called on each row
    • method - tells which method of adapter should be called on each row; defaults to saveRow
  3. Standard customer and product entity parsers

    For most commonly exchanged entities - customer and product - Magento provides also standard parsers: customer/convert_parser_customer (Mage_Customer_Model_Convert_Parser_Customer) and catalog/convert_parser_product (Mage_Catalog_Model_Convert_Parser_Product). Both inherit from Mage_Eav_Model_Convert_Adapter_Entity.

    Since standard adapter's load() methods calls result with array of solely entities' id values it is required to call parser's unparse method, if we want to get more related data. Both parsers take this arrays and for each entity parse its data variable content, ignore system fields, objects, non-attribute fields and create an associative array from the rest. Additionally product parser add to the array result of parsing product related stock item object, and customer parser - result of parsing shipping and billing addresses and information about newsletter subscription.

    Both entities parsers have deprecated parse() methods, since their function is now mostly done by parser actions with standard adapter methods called within parser's context. Example of product parser definition, parsing only products from selected store:

    <action type="catalog/convert_parser_product" method="unparse">
        <var name="store"><![CDATA[1]]></var>
    </action>

  4. Mapping values

    DataFlow module provides also a mapper concept - class with map() method that is responsible for mapping processed fields from one to another. The definition of mapper looks like that for example:

    <action type="dataflow/convert_mapper_column" method="map">
        <var name="map">
            <map name="category_ids"><![CDATA[categorie]]></map>
            <map name="sku"><![CDATA[reference]]></map>
            <map name="name"><![CDATA[titre]]></map>
            <map name="description"><![CDATA[description]]></map>
            <map name="price"><![CDATA[prix]]></map>
            <map name="special_price"><![CDATA[special_price]]></map>
            <map name="manufacturer"><![CDATA[marque]]></map>
        </var>
        <var name="_only_specified">true</var>
    </action>

    Again we have action tag with two attributes: type set as mapper class alias and method that is called to do the mapping. Mapper dataflow/convert_mapper_column is a standard mapper you can find in Magento DataFlow module within app/code/core/Dataflow/Model/Mapper/ folder, and its purpose is to map one array into another with changing the name and posibility to limit fields in result. Map's tag attribute name tells which field name should be replaced in new array by field named like the content of map's tag. If named field doesn't exist in source array, value for target's array field is set to null. Variable _only_specified tells if only fields specified in map definition should be in the resulting array.

This article would be the one that close standard features of DataFlow module and basics of its usage.

Magento widgets

Can't wait to see stable Magento 1.4 . One of the news I am so looking forward are widgets.

What are these you ask?

Widgets allow you to add informational and marketing content on your site easily in administrator panel. For example you will be able to insert links to products, links to categories or links to cms pages, add CMS Blocks, add new, recently compared, recently viewed product lists - and all this in nice and simple way directly in backoffice. You will not need any programming knowledge to add dynamic content on your store pages.

Widgets can be inserted directly on CMS pages:

        Add widget

All you need to do is choose widget type...

        widget insertion

...and see it on your CMS page.

Or you can put widgets on other (not Cms) pages for example cart, product or category page. For that choose from top admin menu CMS > Widgets and click "Add New Widget Instance" button.

Add instance button

Choose the one you want and click continue button. Then you can to specify where widget will be displayed by creating layout updates:

widget

That is it, your widgets are exactly where you need them.