Paypal Express – Skip Order Review Step in Magento 1

Magento has the option to skip the review step upon returning from Paypal (System > Configuration > Payment Methods > Paypal Express Checkout > Advanced Settings > Skip Order Review Step). There are a couple of caveats to the way this functionality works:

1. Using the Paypal button from the cart page

If the button from the checkout/cart page is used rather than following the flow through the one step checkout then the review step becomes compulsory.

We can see this by the URL which the buttons links to the paypal express controller, which has the parameter button/1 on it. In Mage_Paypal_Controller_Express_Abstract, the following is passed into the start method of the checkout (Mage_Paypal_Model_Express_Checkout) object:

$button = (bool)$this->getRequest()->getParam(Mage_Paypal_Model_Express_Checkout::PAYMENT_INFO_BUTTON);
$token = $this->_checkout->start(Mage::getUrl('*/*/return'), Mage::getUrl('*/*/cancel'), $button);

This is then set and saved on the payment object:

$this->_quote->getPayment()->setAdditionalInformation(self::PAYMENT_INFO_BUTTON, 1);

This option is used when the user returns from Paypal in Mage_Paypal_Controller_Express_Abstract‘s returnAction method:

if ($this->_checkout->canSkipOrderReviewStep()) {
} else {

Where the canSkipOrderReviewStep() method uses the PAYMENT_INFO_BUTTON option which was set earlier to decide wither the user should be sent to the review page or have their order placed straight away.

2. Using Paypal’s Incontext Module

If this module from Magento Connect has been installed, then the function referenced above which sets the button parameter when starting the checkout process is commented out:

//$button = (bool)$this->getRequest()->getParam(Mage_Paypal_Model_Express_Checkout::PAYMENT_INFO_BUTTON);
$token = $this->_checkout->start(Mage::getUrl($type . '/return'), Mage::getUrl($type . '/cancel'), true);

This means that Magento will always assume the user came from clicking one of the cart buttons in this scenario, and never from the one page checkout. This line is found in Paypal_InContext_Controller_Express_Action‘s startAction method, which supersedes the one found in Core Magento’s Mage_Paypal_Controller_Express_Abstract.

The reason the InContext module requires the review step is for the user experience. When using the InContext module, the user is not redirected to Paypal on a device where it’s enabled, so would never get chance to review their order from the point they chose to pay either from the cart or checkout page.

Retreiving Configurable Products Whose Children Have a Particular Attribute

A title as catchy as a loose tablecloth. This will give you a list of all configurable products whose children have a particular attribute.

SELECT config_product.entity_id, prod_name.value AS product_name, config_product.sku
     FROM  catalog_product_entity config_product
JOIN eav_attribute
    ON eav_attribute.entity_type_id = 4 
    AND eav_attribute.attribute_code = &quot;name&quot;
JOIN catalog_product_entity_varchar prod_name
    ON prod_name.`attribute_id` = eav_attribute.`attribute_id`
    AND prod_name.`entity_id` = config_product.`entity_id`
WHERE config_product.entity_id IN (
    SELECT parent_id 
        FROM catalog_product_entity_int att_value 
    JOIN `catalog_product_super_link` link
        ON link.product_id = att_value.entity_id
    AND att_value.`value` = attribute_value
  • As Magento only allows the creation of product links on dropdowns, catalog_product_entity_int should be fine to remain hardcoded.
  • Change attribute_value to the option value id. An easy way to retrieve this from Magento’s admin is to look at the name of the input box when looking at the attribute’s Manage Label/Options tab; it will be something like “option[value][959][0]” where 959 is the option ID.

The SQL firstly uses a subquery to get all products which are being used as simples (if their product_id appears in the super_link table), and gets their configurable counterparts from this. We need to do this as a subquery their may be multiple configurables using the same simple product. It then uses the parent IDs in looking up more details from the catalog_product_entity and catalog_product_entity_varchar tables.

Magento’s Configurable Swatches

Create the Attribute

Go to Catalog > Attributes and create the attribute to be used to create the configurable product. This would be typically something like “Colour”. Make sure “Use to create configurable product” is set to “Yes”.

In “Manage Label / Options” add the options for the swatch, E.g. “Red”, “Yellow”, “Blue”. Magento will use these values when looking for the image files for the swatches.

Setting Up The Swatches Config

Go to System > Configuration > Configurable Swatches and set “Enabled” to “Yes”, and select the newly created attribute to be used as a swatch. Set any of the swatch dimensions required if required.

Create the Products

Create the configurable product, and add associated products to it using the attribute we’ve created above. You should now find that when you view your product you get text links output as the colour swatches. This is because Magento currently can’t find any images to use for the colour swatch itself.

Add the Images

There are two ways to get Magento to show images as swatches. The first is to add global swatches which are simply images which are placed in Magento’s **Media/wysiwyg/swatches/” directory using the option label as the filename. So, for example, you may have “blue.png”, “red.png” etc. The extension for image files is PNG.

The second, and this will supersede the first, will allow the setting of a configurable swatch on the product level. To do this, upload images to the configurable product, and then set image’s label field to the attribute’s option value with the suffix “-swatch”, E.g. “blue-swatch”. This image will now be resized and used as the image for that option.

Using Magento’s Configurable Attribute Renderers

Magento decides whether to use a renderer instead of simply outputting configurable options as a select element in catalog/product/view/type/configurable.phtml:

  • All renderers are loaded from the text/list block
  • Each product attribute is looped over, and each of the renderers are looped over within and checked whether it should be rendered for that attribute.
  • The shouldRender of the renderer object is passed in the attribute and jsonConfig. In the case of swatches, Magento returns true if the attribute in question has been defined as a swatch type in System > Configuration > Configurable Swatches > General Settings.

Using this method, we can add other renderers to the block, implement the shouldRender method, and decide whether the use that renderer based on the attribute we’re passed in.

Calculating UK Bank Holidays in PHP

This is from the following page at, copied as a reference here to aid my rapidly depleting hippocampus.


*    Function to calculate which days are British bank holidays (England & Wales) for a given year.
*    Created by David Scourfield, 07 August 2006, and released into the public domain.
*    Anybody may use and/or modify this code.
*    USAGE:
*    array calculateBankHolidays(int $yr)
*    $yr = 4 digit numeric representation of the year (eg 1997).
*    Returns an array of strings where each string is a date of a bank holiday in the format "yyyy-mm-dd".
*    See example below

function calculateBankHolidays($yr) {

    $bankHols = [];

    // New year's:
    switch ( date("w", strtotime("$yr-01-01 12:00:00")) ) {
        case 6:
            $bankHols[] = "$yr-01-03";
        case 0:
            $bankHols[] = "$yr-01-02";
            $bankHols[] = "$yr-01-01";

    // Good friday:
    $bankHols[] = date("Y-m-d", strtotime( "+".(easter_days($yr) - 2)." days", strtotime("$yr-03-21 12:00:00") ));

    // Easter Monday:
    $bankHols[] = date("Y-m-d", strtotime( "+".(easter_days($yr) + 1)." days", strtotime("$yr-03-21 12:00:00") ));

    // May Day:
    if ($yr == 1995) {
        $bankHols[] = "1995-05-08"; // VE day 50th anniversary year exception
    } else {
        switch (date("w", strtotime("$yr-05-01 12:00:00"))) {
            case 0:
                $bankHols[] = "$yr-05-02";
            case 1:
                $bankHols[] = "$yr-05-01";
            case 2:
                $bankHols[] = "$yr-05-07";
            case 3:
                $bankHols[] = "$yr-05-06";
            case 4:
                $bankHols[] = "$yr-05-05";
            case 5:
                $bankHols[] = "$yr-05-04";
            case 6:
                $bankHols[] = "$yr-05-03";

    // Whitsun:
    if ($yr == 2002) { // exception year
        $bankHols[] = "2002-06-03";
        $bankHols[] = "2002-06-04";
    } else {
        switch (date("w", strtotime("$yr-05-31 12:00:00"))) {
            case 0:
                $bankHols[] = "$yr-05-25";
            case 1:
                $bankHols[] = "$yr-05-31";
            case 2:
                $bankHols[] = "$yr-05-30";
            case 3:
                $bankHols[] = "$yr-05-29";
            case 4:
                $bankHols[] = "$yr-05-28";
            case 5:
                $bankHols[] = "$yr-05-27";
            case 6:
                $bankHols[] = "$yr-05-26";

    // Summer Bank Holiday:
    switch (date("w", strtotime("$yr-08-31 12:00:00"))) {
        case 0:
            $bankHols[] = "$yr-08-25";
        case 1:
            $bankHols[] = "$yr-08-31";
        case 2:
            $bankHols[] = "$yr-08-30";
        case 3:
            $bankHols[] = "$yr-08-29";
        case 4:
            $bankHols[] = "$yr-08-28";
        case 5:
            $bankHols[] = "$yr-08-27";
        case 6:
            $bankHols[] = "$yr-08-26";

    // Christmas:
    switch ( date("w", strtotime("$yr-12-25 12:00:00")) ) {
        case 5:
            $bankHols[] = "$yr-12-25";
            $bankHols[] = "$yr-12-28";
        case 6:
            $bankHols[] = "$yr-12-27";
            $bankHols[] = "$yr-12-28";
        case 0:
            $bankHols[] = "$yr-12-26";
            $bankHols[] = "$yr-12-27";
            $bankHols[] = "$yr-12-25";
            $bankHols[] = "$yr-12-26";

    return $bankHols;



header("Content-type: text/plain"); 

$bankHolsThisYear = calculateBankHolidays(2007);



Will output this result:

    [0] => 2007-01-01
    [1] => 2007-04-06
    [2] => 2007-04-09
    [3] => 2007-05-07
    [4] => 2007-05-28
    [5] => 2007-08-27
    [6] => 2007-12-25
    [7] => 2007-12-26

Thanks to David Scourfield!

Setting Category & Product Attribute Defaults in Magento 1

When creating a new attribute, we can specify a default value to be saved alongside that attribute. However, these values do not propagate to categories or products which already exist. We can, however use load_after events to remedy this.

Confix XML


Note: To set the default of a category use catalog_product_load_after instead

class Namespace_Module_Model_Observer
    public function setDefaults($observer)
        $category = $observer->getCategory();

        if($category->getShowSizeRollover() === NULL){

In this example we set the attribute ‘show_size_rollover’ to true if it’s not already been given a value. Because we’ve added this event in the global space, this value will propagate for both frontend and adminhtml.

Requiring Cookies For a Controller Action in Magento

Magento provides a convenient mechanism for the detection and redirection of the user if they’re not enabled. Magento’s preDispatch() method in Mage_Core_Controller_Varien_Action is responsible for this functionality where the current action name is compared against the _cookieCheckActions and the user is redirected if necessary.

To add particular actions to the cookie check, simply add them to the _cookieCheckActions array in the controller’s preDispatch method:

    public function preDispatch()
        $this->_cookieCheckActions[] = 'index';
        $this->_cookieCheckActions[] = 'guest';


And voilà, the user will be redirected to the enable cookies page!

Useful Installer Snippets

To perform database operations from within an installer script, we can get the connection object. Connection objects provide functionality to modify tables, columns, indexes. From model resources, connections can be retrieved using getReadConnection() and _getWriteAdapter(), which is a protected method. Connection objects are instances of the Magento_Db_Adapter_Pdo_Mysql which extends Magento_Db_Adapter_Pdo_Mysql.

All of the following assume that the variable $installer has been assigned as $this from the installer class.

Defining an Installer

        <foo_bar_setup> // Individual namespace for a resource
            <setup>      // Setup information
                <class>Mage_Core_Model_Resource_Setup</class> // Class to run from within

At a first glance it appears odd to have to include the module name in the setup node, however because all XML is merged into one big file, Magento has no way of knowing who the originating module of the setup declaration was.

Getting a table’s name


This calls the Mage::getSingleton(‘core/resource’)->getTableName($tableName) and caches the result for future calls.

Generating Index Names

Although any name could be used as an index name, Magento includes a handy method to automatically generate index names to ensure there are no clashes between them.

$installer->getIdxName($tableName, $fields, $indexType = '');

– Table name can be retrieved with the installer’s getTableName() method.
$fields is an array which contains all of the fields the index is for.
$indexType can be one of the following:


Adding an index to a table


addIndex() accepts three parameters; the first is the index name where we use getIdxName() and pass in the table name, the fields, and the index type. The second parameter an array of columns for the index, and the third is the type of index we’re creating.

Checking a Table Exists


Converting a Date to MySQL Format


Running SQL

$installer->run('DROP table `{$installer->getTable('wishlist/wishlist')}`');

This aliases the connection’s multiQuery method in Varien_Db_Adapter_Pdo_Mysql.
Note: $connection->query() could also be used.

Generating Foreign Key Names

Just like indexes, Magento provides a useful method for generating foreign key names:

$installer->getFkName($priTableName, $priColumnName, $refTableName, $refColumnName) 

Adding a Foreign Key

$installer->getConnection()->addForeignKey($installer->getFkName('wishlist/wishlist', 'customer_id', 'customer/entity', 'entity_id'),
        'customer_id', $installer->getTable('customer/entity'), 'entity_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE

– The first parameter is the foreign key name, which we generate using Magento’s getFkName() method.
– The second and third parameters are the primary table and column.
– The fourth and fifth parameters are the reference table and column.
– The sixth parameter is the database action to perform when the primary column is deleted.
– The seventh parameter is the database action to perform when the primary column is updated.

Possible options for the onDelete and onUpdate parameters are:


Removing a Foreign Key

As far as I’m aware, Magento doesn’t offer a utility method for removing foreign keys. Instead, we can use the run command and build the foreign key’s name with getFkName().

$wishlistTable = $installer->getTable('wishlist/wishlist');
$fkName = $installer->getFkName('wishlist/wishlist', 'customer_id', 'customer/entity', 'entity_id');

$installer->run("ALTER TABLE {$wishlistTable} DROP foreign key {$fkName}", array());

Adding a Column to an Existing Table

		'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
		'length' => 255 // Will be created as VARCHAR
		'nullable' => true,
		'default' => null,
		'comment' => 'Created At’ // A comment must be provided

– The first column is the table name and the second is the name of the column we’re adding. The third is an array of attributes for the new column; a list of types can be ascertained from the Varien_Db_Ddl_Table class.
Note: To create a column with a type of varchar, use TYPE_TEXT with a length of 255. TYPE_VARCHAR, has been deprecated and the constant is only kept in for the MySQL adapter.

Column Types

const TYPE_BOOLEAN          = 'boolean';
const TYPE_SMALLINT         = 'smallint';
const TYPE_INTEGER          = 'integer';
const TYPE_BIGINT           = 'bigint';
const TYPE_FLOAT            = 'float';
const TYPE_NUMERIC          = 'numeric';
const TYPE_DECIMAL          = 'decimal';
const TYPE_DATE             = 'date';
const TYPE_TIMESTAMP        = 'timestamp'; // Capable to support date-time from 1970 + auto-triggers in some RDBMS
const TYPE_DATETIME         = 'datetime'; // Capable to support long date-time before 1970
const TYPE_TEXT             = 'text';
const TYPE_BLOB             = 'blob'; // Used for back compatibility, when query param can't use statement options
const TYPE_VARBINARY        = 'varbinary'; // A real blob, stored as binary inside DB

Displaying Magento’s Configurable Options as Radio Buttons (Quick and Dirty)

By default Magento displays a product’s configurable options as select drop downs. This isn’t always appropriate for the project however; some clients prefer radio buttons, and longer product names will cause some of the text in the select box to be cut off on mobile devices.

Here’s a quick and dirty way to display those selects as radio buttons (this assumes a descendent of the RWD theme is being used and hasn’t been tested with a base theme). We don’t mess about with changing templates with this method – it’s all done in JavaScript.


        <reference name="head">
            <action method="addItem">

JavaScript – configurable.js

    var loadOptions = Product.Config.prototype.loadOptions;
    var containerClass = "options-list"

    // Create an option array for each of the configurable options
    Product.Config.prototype.loadOptions = function() {
        var self = this;

            var attributeId =[a-z]*/, '');
            var options = self.getAttributeOptions(attributeId);
            var radioId = attributeId + "-radio";

            var $optionContainer = $j('<ul />').addClass(containerClass);

                for(var i=0;i<options.length;i++){
                    var $radio = $j("<input type='radio' />" ).attr("name", radioId).addClass('radio');
                    var $li = $j("<li />");
                    var foundValue = $j(element).find('option[value="' + options[i].id + '"]').html();
                    var $span = $j("<span class='label' / >").append($j('<label />').html(foundValue));

                    $radio.attr('value', options[i].id);


                    $radio.on('click', function(ev){
                        var val = $j(this).val();

                'height': '0px',
                'position': 'absolute'
  • We monkey patch the Product.Config object and add our own loadOptions method. This runs the original method, then recreates the select as a list of radio options in the same container as where the select box resides.
  • The select box is then hidden, however instead of applying a display: none on it, we change its visibility, height and position. This because Magento will not validate hidden options by default so appling this method allows us to keep Magento’s built in validation.
  • When the user changes the selected radio button, all the script does is changes the value of the hidden select box and then calls the reloadPrice() method.

Useful Snippets

Factory Methods


About Magento



// Get the increment id of the current user's last order
$lastOrderId = Mage::getSingleton('checkout/session')->getLastRealOrderId();

// Load the last order for the current user
$order = Mage::getModel('sales/order')->loadByIncrementId($lastOrderId);

Data store

Mage::register('name', 'value')

Paths & URLs

Mage::getRoot(); // Application root absolute path
Mage::getBaseDir($type); // The base dir of the install pass in 'media', 'app', 'base', 'design', 'etc', 'lib', 'locale', 'skin', 'var', 'tmp', 'cache', 'log', 'session', 'upload', 'export'
Mage::getUrl($route, $params); // Get a url (uses core/url model)
Mage::getBaseUrl($type); // The base url of the install

The following constants can be passed into the getBaseUrl() method:
Mage_Core_Model_Store::URL_TYPE_WEB – Store URL

Mage::getModuleDir($type, $moduleName); // Get the module path
$type can be 'etc', 'controllers', 'sql', 'locale'.  If manually including an controller file, use this method as they are not autoloaded.
Mage::helper('core/url')->getCurrentUrl(); // Get the current URL
Mage::helper('core/url')->getHomeUrl(); // Get the home URL
Mage::helper('checkout/url')->getCheckoutUrl() // Get the checkout URL
Mage::getUrl('checkout/cart') // Get the cart URL using getUrl

URLs in a block

$this->getSkinUrl('path/to/resource.png'); // Normal
$this->getSkinUrl('images/ sampleimage.gif', array('_secure'=>true)) // Secure

Redirecting within a controller

$this->_forward($action, $controller = null, $module = null, array $params);

Alternate redirect method



Mage::app()->getStore(); // Get the current Store object


Mage::getStoreConfig($path, $store = null); // Retrieve a config value, store is the default store by default
Mage::getStoreConfigFlag($path, $store = null); // Retrieve a boolean config value
Mage::getConfig() Get the config object

Observers & Events

Mage::addObserver($eventName, $callback, $data, $observername, $observerClass);
Mage::dispatchEvent($name, $data);


Mage::exception($module, $message, $code);
Mage::throwException($message, $messageStorage);
Mage::logException(Exception $e);


Mage::log($message, $level, $file, $forceLog); // Logging var/log - use file from dev/log/file
// Will be logged in system.log unless $file is specified.  Won’t be logged if dev/log/active is off in the admin, unless $forceLog is true
Mage::log($message, $level, $file, $forceLog);




Mage::helper('core/http')->getRemoteAddr(); // Get the user's IP address

Product Image Resizing

Mage::helper('catalog/image')->init($product, $attributeName, $imageFile = null)
->resize($width, $height)
->keepAspectRatio(bool) // The image won't be stretched
->keepFrame(bool) // The image won't be cropped
->constrainOnly(bool) // The image will only be scaled down

$attributeName is a media attribute. Incidentally, a watermark will automatically be added to catalog images if the admin control has been turned on.

Image Resizing Using Varien_Image

$_imageUrl = Mage::getBaseDir(‘media’) . DS . $image;

$imageResized = Mage::getBaseDir(‘media’) . DS . "resized" . $image;

$imageObj = new Varien_Image($_imageUrl);




$imageObj->resize(140, 140);



  • Products are assigned to a Website. Products aren’t visible unless they are.
  • Products can’t be set to enabled / disabled on a store view.
  • Store Groups can contain a different root catalog, but the same products will still be available.

EAV Attributes

// Load a product attribute object baset on its code (replace 'catalog_product' to load other attribute types
$attributeModel = Mage::getModel('eav/entity_attribute')->loadByCode('catalog_product', 'attribute_code');

Outputting Product Attributes

// For Dropdowns:

// For any attribute

// Formatting Attributes
Mage::helper('catalog/output')->productAttribute($product, $attributeHtml, $attributeName);

Formatting attributes respects any product attribute settings such as encoding HTML output

Setup and Installers

  • data installers go in the module’s data/ folder
  • installers go in the module’s sql/ folder

Running indexers within installers

They won’t run properly unless the store is set to admin and the following is set:

Mage::register('isSecureArea', 1);

Controller Events

The following are dispatched from the preDispatch method of Mage_Core_Controller_Varien_Action
– *controller_action_predispatch_routeName * Eg. controller_action_predispatch_catalog
– *controller_action_fullActionName * Eg. controller_action_predispatch_catalog_product_view

Controllers, Actions & Request Object

// Get the module, controller and action name separated by underscores. Used to attach design XML elements to pages. Eg. catalogsearch_result_index

// Get the current action class (outside of the controller)

Translate Inline & Emails

Before sending an email, translate inline should be turned off to prevent input boxes being rendered in the email’s contents. It should be turned back on afterwards.

$translate = Mage::getSingleton('core/translate');


The flow of Magento’s routing begins from Mage_Core_Controller_Varien_Front which calls *dispatch** which then attempts to match against a router.

The standard routers included in Magento are:
– Mage_Core_Controller_Varien_Router_Standard (Frontend)
– Mage_Core_Controller_Varien_Router_Admin (Admin)
– Mage_Core_Controller_Varien_Router_Default (Default)

Which then sets the following on the request object:


Store Information

Get the Admin Store Model

<?php Mage::getModel('core/store')->load('admin', 'code');

Get the Base Website

<?php Mage::getModel('core/website')->load('base', 'code');

Get the Admin Store ID (Constant)

<?php Mage_Store_Model_App::ADMIN_STORE_ID;


Reindex everything

/* @var $indexCollection Mage_Index_Model_Resource_Process_Collection */
$indexCollection = Mage::getModel('index/process')->getCollection();
foreach ($indexCollection as $index) {
/* @var $index Mage_Index_Model_Process */

Reindex a Particular Indexer

$process = Mage::getModel('index/indexer')->getProcessByCode('catalog_product_price');

Temporarily disable a cache type

In particular instances, it may be necessary to disable a type of layout cache. For instance, you may AJAX a call to a page which loads a layout handle for the root node and returns it as an JSON response. It may be it necessary to later load another layout handle as the root node, but with layout caches on this erroneously loads the first layout handle. The following solution will temporarily prevent the layout XML from being cached:

    protected function _disableLayoutCache()
        $cache = Mage::app()->getCacheInstance();