magento how-to

Magento Events

When it comes to extending Magento core functionality you have two options - override core classes or use event-driven architecture. The major disadvantage of first is that you can override class only once, so if you want to override it in multiple modules you are soon going to find yourself in dependency hell. Event-driven architecture allows you to keep loose coupling without losing the flexibility of extending Magento modules.

When you want to use Magento event-driven architecture you must know basically two things - how to dispatch an event and how to catch it.

Dispatching events

Within Magento you can dispatch an event as simple as by calling Mage::dispatchEvent(...) method, for example:

 Mage::dispatchEvent('custom_event', array('object'=>$this));

This methods accepts two parameters - event unique identifier and associative array of data that is set as Varien_Event_Observer object data, so in fact passed to event observers.

Catching events

Catching events is a little bit more complex than dispatching. You have to use existing custom module or create a new one. In the minimal case the module file tree should look like this:

  

Within config.xml you have to add a definition of event observer. Which of the main config.xml file sections (frontend, adminhtml) should contain this definition depends on the scope you want your observer to work on. Here is the example of definition:

<events>
      <custom_event> <!-- identifier of the event we want to catch -->
        <observers>
          <custom_event_handler> <!-- identifier of the event handler -->
            <type>model</type> <!-- class method call type; valid are model, object and singleton -->
            <class>baobazacustommodule/observer</class> <!-- observers class alias -->
            <method>customObserverAction</method>  <!-- observer's method to be called -->
            <args></args> <!-- additional arguments passed to observer -->
          </custom_event_handler>
        </observers>
      </custom_event>
</events>

Xml above should be self-explanatory. I will just explain that for type model and object are equal in behavior and mean that object of a class will be instantiated using Mage::getModel(...) method, and singleton means it will be instantiated using Mage::getSingleton(...) method.

Observer.php file should contain relevant observer class. There is no interface nor need to extend any class for observer classes. The method though should accept one parameter which is the object of Varien_Event_Observer class. This object is the link between dispatcher and event handler. It inherits from Varien_Object so has all required getters handled magically. For example:

class Baobaz_ACustomModule_Model_Observer
{
  public function customObserverAction(Varien_Event_Observer $observer)
  {
    $object = $observer->getEvent()->getObject(); // we are taking the item with 'object' key from array passed to dispatcher
    $object->doSomething();

    return $this;
}

Default events

Magento implements lot of events. You can find list of them here. What you may miss reading this list, and what was spotted on MageDev blog, Mage_Core_Model_Abstract by default dispatch some special events. Those are:
 

event identifier event parameters
model_save_before 'object'=>$this
{_eventPrefix}_save_before {_eventObject}=>$this
model_save_after 'object'=>$this
{_eventPrefix}_save_after {_eventObject}=>$this
model_delete_before 'object'=>$this
{_eventPrefix}_delete_before {_eventObject}=>$this
model_delete_after 'object'=>$this
{_eventPrefix}_delete_after {_eventObject}=>$this
model_load_after 'object'=>$this
{_eventPrefix}_load_after {_eventObject}=>$this

 

{_eventPrefix} means the value of $_eventPrefix variable and {_eventObject} means the value of $_eventObject variable. All classes inheriting from Mage_Core_Model_Abstract should override these variables to create specific events being dispatched. For example for catalog cagetory these variables take following values: $_eventPrefix = 'catalog_category';  $_eventObject = 'category';

Magento Checkout – How to customize the design

Customize checkout page!One of the most important parts of any e-commerce solution, including Magento, is checkout process. The number of orders is higly dependent on the simplicity, usability and even look of the checkout pages. That's why shop owners often decide to create custom design of those pages in their Magento website. With Magento templates system you can easily customize the look of any page, but in case of checkout, you have to keep some default constructions.

You can find the default template of checkout stages in app/design/frontend/.../.../templates/checkout/onepage.phtml. Here's the fragment of html output of that template:

<ol id="checkoutSteps" class="one-page-checkout">
    <li id="opc-login" class="section">
        <div class="head">
            ...<!-- head of  login section -->
        </div>
        <div id="checkout-step-login" class="box a-item" style="display:none;">
            ...<!-- body of login section -->
        </div>
    </li>
    ... <!-- next sections here -->
</ol>

As we can see, the checkout process is enclosed in an ordered list (<ol>...</ol>). It is quite a good idea, as the order of steps is very important here. Moreover, the default javaScript scripts are strongly related to the html structure (especially to the names of classes – we will look at them later). So it is suggested to preserve this default structure of an ordered list.

To improve the usability of the process, it is a good habit to give a different look to checkout steps headers, depending on the currently active stage. It is an easy task in Magento, because the included script dynamicaly adds or removes class names to the <li class="section"> elements of <ol id="checkoutSteps">. Those class names depend on the currently active step of checkout:

  • no additional class for all next steps after the current step
  • class="allow" for all previous steps
  • class="allow active" for the current step

A similar mechanism is applied in checkout progress - an additional column containing data of already passed checkout steps (billing address, shipping address, shipping method and payment method). The content of this column is changed dynamically, too, according to the current step. The example below show the checkout progress block's html structure after the "billing information" checkout step:

<div class="box one-page-checkout-progress">
    <h3>Your Checkout Progress</h3>
    <ol>
        <li>
            <h4 class="complete">Billing Address                  
                <span class="separator">|</span>                  
                <a href="#billing" onclick="checkout.accordion.openSection('opc-billing'); return false;">Change</a>
            </h4>
            <div class="content">
                ... <!-- billing address here -->
            </div>
        </li>
        <li>
            <h4>Shipping Address</h4>
        </li>
        <li>
            <h4>Shipping Method</h4>
        </li>
        <li>
            <h4>Payment Method</h4>
        </li>
    </ol>
</div>

The checkout progress is an ordered list, too. The steps that are not yet passed contain only <h4> title. In the other steps there is a class="complete" added to <h4> so we can make the titles of previous steps look different then the titles of next steps.

Those are the basis of the Magento checkout page structure. Understanding and remembering that strucutre can be very helpful during custom Magento shop development.

How to use Magento Shipping Table Rates

With Magento you can set few kinds of shipping methods: flat rate, table rates or even real-time carrier rates from UPS, FedEx and DHL. You can also use free shipping discounts, that can be created for order amounts, or as part of specific marketing promotions.

To set shipping methods in backoffice go to System -> Configuration and choose from the left navigation "Shipping methods". When you want to use Table rates you can choose one of three conditions avalaible:

  • Weight vs. Destination
  • Price vs. Destination
  • Number of Items vs. Destination

You also need to create csv file for your table rates. You can first export one from magento to have a template. To do that you will need to change scope for your website in "Current Configuration Scope" (top left select box). Choose "Main website" for example. Then in Table rates you will be able to see "Export CSV" button.

Export table rates

Export and save tablerates.csv on your computer. The CSV file should looks like:

"Country","Region/State","Zip/Postal Code","Weight (and above)","Shipping Price"
"FRA","*","*","0.0000","11.0000
"FRA","*","*","10.0000","13.000"
"FRA","*","*","20.0000","15.0000"

Above lines define shipping rates for all regions in France. As you see weight condition is set as "from and above". So when order wieght is 0 and above (0-10 kg) shipping wil cost 11 euros, when its 10 and above (10 - 20 kg) shipping is 13 euros. When order weight is above 20 kg you will pay 15 euros for shipping. Even when it's 100 kg or 1000 kg you will still pay 15 euros! The problem with condition "from and above" is that you are unable to set maximum weight. So lets make these conditions work in "up to" way.
In "up to" way tablerates.csv like:

"Country","Region/State","Zip/Postal Code","Weight (and above)","Shipping Price"
"FRA","*","*","10.0000","13.000"
"FRA","*","*","20.0000","15.0000"

will define that for order which weight is up to 10 kg (0-10 kg) shipping cost is 13 euros. For orders up to 20 kg (10 - 20 kg) it's 15 euros. So the maximum weight for table rate is 20kg. Above 20kg you will have no table rate available.

To make table rates work that way function getRate() from Mage_Shipping_Model_Mysql4_Carrier_Tablerate needs to be overwriten. First we need to create new module under /code/local directory and configure it:

Magento module

<?xml version="1.0"?>
<config>
    <global>
        <models>
            <shipping_mysql4>
                <rewrite>
                    <carrier_tablerate>Baobaz_Shipping_Model_Mysql4_Carrier_Tablerate</carrier_tablerate>
                </rewrite>
            </shipping_mysql4>
        </models>
    </global>
</config>

 

Our module needs to be activated by creating file Boabaz_Shipping.xml in app/etc/modules directory:

<?xml version="1.0"?>
<config>
    <modules>
        <Baobaz_Shipping>
            <active>true</active>
            <codePool>local</codePool>
        </Baobaz_Shipping>
    </modules>
</config>

In new module create class Baobaz_Shipping_Model_Mysql4_Carrier_Tablerate that will extend core magento class Mage_Shipping_Model_Mysql4_Carrier_Tablerate.

class Baobaz_Shipping_Model_Mysql4_Carrier_Tablerate extends Mage_Shipping_Model_Mysql4_Carrier_Tablerate
{}

Then lets copy/paste getRate(Mage_Shipping_Model_Rate_Request $request) function from core class and change few last lines at the end of the function:

$select->where('condition_value<=?', $request->getData($conditionName));

$select->order('condition_value DESC');

to:

$select->where('condition_value>=?', $request->getData($conditionName));

$select->order('condition_value ASC');

That will make table rates conditions work in "up to" way.

Developing module for Magento Tutorial - Where to Begin [Part 1]

Have you ever tried to read the complete list of features Magento comes with? Gosh, it's huge. And it's even more impressive when you look at Magento Connect and all the extensions available there. However, if you are reading this blog entry you are probably looking for something which is not available yet. Or maybe you are a developer who just loves to play with the code. Either way, for serious business reasons or just because of your hack-ish nature, developing a Magento module is fun.

Let's create something really really simple. The module. You might wonder what that nifty module will be doing. Well in this fist part it will does nothing but being declared in Magento!

Where to begin?

Take a loot at the directories structure of your Magento installation. In app > code > local, create a directory, which will be a kind of container for your modules, in Magento it's usually called code pool. If you wonder how to name it, I can say that company name you're working for is probably a good one. It is in my case ;)

So let's create it

$ cd app/code/local/
$ mkdir Baobaz

Then, in this container we should create the module. For the purpose of this blog let's call it "Reader" (in next few parts the reason of using this name should clarify, if not... well.. it's still sounds good ;) )

$ cd Baobaz
$ mkdir Reader

So far, so good. The next step is a little bit more complex, but still simple. Let's change current directory...

$ cd ../../../etc/modules/

Hm... where are we?

$ pwd /Your/favorite/place/for/web/projects/magento/app/etc/modules/

Now, you must tell Magento about the module. To do this you must create an xml file. Take your favorite editor and create a file called Baobaz_Reader.xml (as you probably guessed the name consists of <container_name>_<module_name>.xml

$ vi Baobaz_Reader.xml

Now put this into your file

<?xml version="1.0"?>
<config>
    <modules>
        <Baobaz_Reader>
            <active>true</active>
            <codePool>local</codePool>
        </Baobaz_Reader>
    </modules>
</config>

Save file. And.... Voilà! That's one small step for a man, one giant leap for man...gento. :-)

To be sure that Magento knows about our new module let's login to admin panel. Navigate to System > Configuration, choose Advanced, from Advances section (yes, it's on the left).

You should see Baobaz_Reader at the top (or somewhere there) of the "Disable modules output" list.

OK, OK, OK... I admit that our nifty module makes nothing so far but be patience, in a few next parts we will add some more useful features.

To be continued...