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.

Custom JavaScript form validators

Magento, thanks to Prototype framework, has nice set of JavaScript validators - so we can validate user input without reloading site. Of course, we cannot rely only on it, but it is good starting point - and for majority of users, it will have good look'n'feel.

But sometimes bulit-in validators are just not enough - for example, we can add custom fields for customer account, address etc. and we need some custom validation there. Magento together with Prototype gives us quick and easy way.

Little bit of theory - Prototype gives us Validation class. To check if form is valid, we have to call validate() method, which checks validation classes. I say classes, cause those are defined by css classes for input, for example:

<input name="someinput" class="required-entry" />  

will require to be not-empty. It would be nice to add our own classes to keep this process so simple as it is. So...

Step 1.

Create your own file, for example /skin/frontend/default/your_skin/js/myvalidation.js and add it to page.xml layout file.
Important note: it must be somewhere below adding script prototype/validation.js - usually, end of head block is good and safe place.
 

Step 2.

Editing myvalidation.js. Take a look on following code:

if(Validation) {        //   1.
    Validation.addAllThese([      //    2.
        [
            'validation-myown',       //     3.
            'Please insert proper word',     //    4.
            function(v,r){ return v.indexOf('valid')==-1?false:true }   //  5.
        ],
       [ ]    // 6.
    ])
}

Little explanations.

  1. We don't want to make any JavaScript error if for some reason validation script is not enabled, so we ensure it exists
  2. Adding a validators. Argument is array, each value of it is also array - so you can add multiple validators at once.
  3. This is css class that validator will be searching for, note that it must begin with validation-  ('-' sign at end) - otherwise it may not work as desired.
  4. This is error message displayed when validation is not passed.
  5. Finally, our most important function - validator itself.
  6. Next validators...

In this example validator function takes two arguments, but in fact usually first one is enough.
So first - called v here - is value of input at moment of validation
Second - called r here - can be useful in very custom validators - it is reference to validated object.
When validator function returns true, validation for field is passed, if false - opposite.

That's it. Quite simple if you will understand process ;)
 

Step 3. (optional)

We have nice error message - but what about case we need to translate it? Well, we can use Magento JavaScript class - Translate. Adding translations is quite simple, but has to be done from template level. We can use Jakub's module (blog.baobaz.com/en/blog/magento-translations-module):

<script type="text/javascript">
    Translator.add('Please insert proper word','<?php echo Mage::helper('translations')->__('Please insert proper word')?>');
</script>

Add this to template where you use your custom validators or create special block for it, so they can be easily reused.

Anything more on validators is just plain JavaScript and your needs :)

Magento checkout progress with custom layout

Recently, while working on Magento checkout process, I stumbled on two issues related to the "one page checkout" solution used on sites Magento. The problems are maybe not very difficult to solve, but as they repeat in different projects, I think they deserve mentioning. In this article I'll shortly explain the checkout progress mechanism in Magento, I'll investigate why it doesn't work with custom layout, and how to fix it.

As you can see, Magento checkout page contains two columns:

  1. main column, consisting of checkout steps
  2. side column with „checkout progress”, list of all currently entered data in checkout process (billing adress, shipping adress, shipping method, payment method)

The checkout progress column is updated by javascript each time any checkout step is completed. But if you decide to set different layout for checkout page than the default "two-columns-right" (default in Community Edition; in Enterprise Edition we've got "one column" layout here), the checkout progress column will not be updated any more. Why?

To find an answer on this mysterious riddle, open the file /skin/frontend/default/default/js/opcheckout.js. This is the javascript mechanism controlling behaviour of checkout process. About line 50 you can find function reloadProgressBlock:

reloadProgressBlock: function(){
    var updater = new Ajax.Updater(
        $$('.col-right')[0],
        this.progressUrl,  
        {method: 'get', onFailure: this.ajaxFailure.bind(this)}
    );
},

This function updates the content of the right column, filling it with the actual checkout progress status. So if the layout of checkout page is "two columns right", everything works fine. But in other case the function fails – it can't find the DOM element with class="col-right", specified by $$('.col-right')[0] javascript statement.

Now the solution is obvious – to replace $$('.col-right')[0] by the selector of your wrapper element containing <div class="one-page-checkout-progress">. So, for example, if your HTML structure looks like that:

<div id=”my-custom-right-column”>
        <div class=”one-page-checkout-progress”>
        ...
        </div>
</div>

then you have to copy the default opcheckout.js file, put it into your theme's appriopriate folder, and edit the reloadProgressBlock function, by changing the $$('.col-right')[0] into $('my-custom-right-column'):

reloadProgressBlock: function(){
    var updater = new Ajax.Updater(
        $('my-custom-right-column'),
        this.progressUrl,
        {method: 'get', onFailure: this.ajaxFailure.bind(this)}
    );
},

Moreover, the Ajax.Updater() accepts also an id of an object as first argument, so if you select wrapper by ID, you can simplify the code, removing the $() Prototype element selector:

reloadProgressBlock: function(){
    var updater = new Ajax.Updater(
        'my-custom-right-column',
        this.progressUrl,
        {method: 'get', onFailure: this.ajaxFailure.bind(this)}
    );
},

That's all. It works :)

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 Backoffice (Admin Panel) Options - [Part 3]

In the previous part (Magento Backoffice (Admin Panel) Options - [Part 2]) we created a module and added some code to manage the module from "Admin Panel". A new menu entry is now responsible for modifications of the module settings. But Magento allows us to use a different approach.

Every module can contain a configuration file named system.xml. In this file we can define tabs and sections which will be later placed in System->Configuration, we define their content also. Data entered in the sections is stored in core_config_data table. Handling the data between the sections and the database in done by Magento, so it's a quite handy solution if we just need to put the data inside the database.

First, we need to create system.xml in module etc directory. The file should contain following definitions: what will be the tab for our module management, what sections it will contain and what is the content of each section. Our file is quite simple. First, the tabs:

<tabs>
    <settime>
        <label>Set Time</label>
        <sort_order>100</sort_order>
    </settime>
</tabs>

Every tab has its name, label which will be displayed, and number corresponding to the order of all tabs collection. Next we define sections, which can be compared to submenus of the tab:

<sections>
    <setit>
    </setit>
</sections>

And then we put inside sections their content:

<class>separator-top</class>

Section label:

<label>Set It!</label>

Name of the tab containing the section:

<tab>settime</tab>

Data type:

<frontend_type>text</frontend_type>

Sort order - important if there is more than one section within a tab:

<sort_order>40</sort_order>

Should the section be visible when we define default configuration:

<show_in_default>1</show_in_default>

Should the section be visible when we define configuration for a website:

<show_in_website>1</show_in_website>

Should the section be visible when we define configuration for a store:

<show_in_store>1</show_in_store>

Time for section content. Groups are containers for similar data fields:

<groups>
    <settingtime>
        <label>Set Time</label>
        <frontend_type>text</frontend_type>
        <sort_order>100</sort_order>
        <show_in_default>1</show_in_default>
        <show_in_website>1</show_in_website>
        <show_in_store>1</show_in_store>
        <fields>
            <time_format>
                <label>Time Format</label>
                <comment>string according to PHP date() argument</comment>
                <frontend_type>text</frontend_type>
                <sort_order>1</sort_order>
                <show_in_default>1</show_in_default>
                <show_in_website>1</show_in_website>
                <show_in_store>1</show_in_store>
            </time_format>                        
        </fields>
    </settingtime>
</groups>

So now system.xml looks like that:

<config>
    <tabs>
        <settime>
            <label>Set Time</label>
            <sort_order>100</sort_order>
        </settime>
    </tabs>
    <sections>
        <setit>
            <class>separator-top</class>
            <label>Set It!</label>
            <tab>settime</tab>
            <frontend_type>text</frontend_type>
            <sort_order>40</sort_order>
            <show_in_default>1</show_in_default>
            <show_in_website>1</show_in_website>
            <show_in_store>0</show_in_store>
            <groups>
                <settingtime>
                    <label>Set Time</label>
                    <frontend_type>text</frontend_type>
                    <sort_order>100</sort_order>
                    <show_in_default>1</show_in_default>
                    <show_in_website>1</show_in_website>
                    <show_in_store>1</show_in_store>
                    <fields>
                        <time_format>
                            <label>Time Format</label>
                            <comment>string according to PHP date() argument</comment>
                            <frontend_type>text</frontend_type>
                            <sort_order>1</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>1</show_in_store>
                        </time_format>                        
                    </fields>
                </settingtime>
            </groups>
        </setit>
    </sections>
</config>

But we need something more to make it work - modification in config.xml. We need to add to it information about configuration resources that are related to our module.

<acl>
    <resources>
        <admin>
            <children>
                <system>
                    <children>
                        <config>
                            <children>
                                <setit>
                                    <title>Setit Section</title>
                                </setit>
                            </children>
                        </config>
                    </children>
                </system>
            </children>
        </admin>
    </resources>
</acl>

As you can see, we define path to our section. The path will be also relevant for saving or loading data from database, because it is used as an identifier. In our case data entered in field time_format wil have identifier setit/settingtime/time_format.

Magento will add the sections to permissions management in System->Permissions->Roles->Role Resources. Check this tab, our new sections should be listed there when you choose 'Custom'. If you see 'Access Denied' error when you click on 'Set It!' section, then go to System->Permissions->Roles, select Administrators and Role Resources. Change All to Custom, mark all checkboxes - be careful, because mistake in this step may cause inability to login to Admin Panel. Save, then go back and change Custom back to All and save again. Now you should have fully functional module management.

Here is a screenshot of final result in Magento back office:

Set it screenshot