Retrieve a list of all parent category url keys in Magento

In this example, we only want to show a block for a product which resides within a particular category. We check all of the category’s parent categories and show the block if any of them match our $_allowedCategories array. Using URL keys instead of category IDs makes this code more portable between environments such as development and production where IDs may not be the same.

class Namespace_Module_Block_Catalog_Product_Request_Form extends Mage_Core_Block_Text_List
    protected $_allowedCateogies = array('shelters');

    protected function _toHtml()
        $allCatUrls = array();

        // get an array of all parent category url keys
        if($product = Mage::registry('current_product')){
            $cats = $product->getCategoryCollection()->addAttributeToSelect('url_key');

            foreach($cats as $cat){
                $this->_getParentUrlKeys($cat, $allCatUrls);

        // Check whether the category is in the array
        foreach($this->_allowedCateogies as $category){
            if(in_array($category, $allCatUrls)){
                return parent::_toHtml();

        return '';

    // Recursively get all of the parents url keys
    protected function _getParentUrlKeys(Mage_Catalog_Model_Category $cat, &$urlKeys)
        if(!in_array($cat->getUrlKey(), $urlKeys)) {
            $urlKeys[] = $cat->getUrlKey();

            return $this->_getParentUrlKeys($cat->getParentCategory(), $urlKeys);

Fixing Magento’s Configurable Attribute Ordering

Somewhere along the way in a 1.9.x update, configurable attribute sorting stopped working on the fronted. Magento should use an attribute’s position parameter (in the options tab when editing a product attribute), to order the dropdown on a product detail page.

The issue appears to be where Magento assigns prices to an attribute collection in Mage_Catalog_Model_Resource_Product_Type_Configurable_Attribute_Collection

Magento first gets the options for the attribute here:

 $options = $productAttribute->getFrontend()->getSelectOptions();

At this point, all is good. In Magento’s SQL to get the options, sort_order is used correctly and the array of options is in the correct order.

The problem seems to occur in the loop on line 257:

 foreach ($this->getProduct()->getTypeInstance(true)
->getUsedProducts(array($productAttribute->getAttributeCode()), $this->getProduct())
   as $associatedProduct) {

The order of the attributes will now be determined by the order of items in this loop, which is the order of associated products.

What we need to do is re-order the $values array which is populated in the above loop, we can do this using the order of the $options array:

$order = 0;
$optionsByValue = array();
foreach ($options as $option) {
    $optionsByValue[$option['value']] = $option['label'];
    $orders[$option['value']] = $order;

Here we utilise the existing loop which populates the $optionsByValue array to log the ordering of each option. We can then use those values by passing them into our custom sort method just before Magento adds the prices to each item:

/* Resort the values array using the order we've logged against options */
usort($values, array($this, 'sortValues'));

foreach ($values as $data) {

Here’s the sort method which puts the array back to the correct sort order:

public function sortValues($value1, $value2){
    if(isset($value1['sort_order']) && isset($value2['sort_order'])){
        if($value1['sort_order'] > $value2['sort_order']){
            return 1;

        if($value1['sort_order'] < $value2['sort_order']) {
            return -1;

        return 0;

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.

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) 

Renaming a Column

$installer->run("ALTER TABLE table_name CHANGE `column_name` `new_column_name` VARCHAR(255);

Adding a Foreign Key When Creating A Table

$table->addForeignKey($installer->getFkName('catalogsearch/result', 'product_id', 'catalog/product', 'entity_id'),
        $installer->getTable('catalogsearch/result'), 'product_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)

– Would create a from the catalogsearch/result table to the catalog/product table from the column product_id to entity_id

Adding a Foreign Key to an Existing Table

    $installer->getFkName('catalogsearch/result', 'product_id', 'catalog/product', 'entity_id'),

– 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


Check a Module is Enabled



// 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), possible params are _secure => true, and _forced_secure => true

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

Formatting a string as a URL

Mage::getModel(‘catalog/product_url’)->formatUrlKey(‘string to be converted’);

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');