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

<global>
    <events>
        <catalog_category_load_after>
        	<observers>
        		<observer_name>
        			<type>singleton</type>
        			<class>module/observer</class>
        			<method>setDefaults</method>
        		</observer_name>
        	</observers>
        </catalog_category_load_after>
    </events>
</global>

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

<?php
class Namespace_Module_Model_Observer
{
    public function setDefaults($observer)
    {
        $category = $observer->getCategory();

        if($category->getShowSizeRollover() === NULL){
            $category->setShowSizeRollover(true);
        }
    }
}

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:

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

        parent::preDispatch();
    }

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

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

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

<?php
$installer->getTable('wishlist/wishlist');

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.

<?php
$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:

<?php
Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
Varien_Db_Adapter_Interface::INDEX_TYPE_PRIMARY
Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX
Varien_Db_Adapter_Interface::INDEX_TYPE_FULLTEXT

Adding an index to a table

<?php
$installer->getConnection()->addIndex($installer->getTable('wishlist/wishlist'),
    $installer->getIdxName($installer->getTable('wishlist/wishlist'),
        array('customer_id'),
        Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
    ),
    array('customer_id'),
    Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
);

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

<?php
$installer->getConnection()->isTableExists($installer->getTable('wishlist/wishlist'));

Converting a Date to MySQL Format

<?php
$installer->getConnection()->convertDate($date);
$installer->getConnection()->convertDateTime($dateTime);

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:

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

Adding a Foreign Key

<?php
$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:

<?php
Varien_Db_Adapter_Interface::FK_ACTION_CASCADE
Varien_Db_Adapter_Interface::FK_ACTION_SET_NULL
Varien_Db_Adapter_Interface::FK_ACTION_NO_ACTION
Varien_Db_Adapter_Interface::FK_ACTION_RESTRICT
Varien_Db_Adapter_Interface::FK_ACTION_SET_DEFAULT

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().

<?php
$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

$this->getConnection()->addColumn(
	$this->getTable('wishlist/wishlist'),
	'cookie_id',
	array(
		'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

<?php
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.

XML

    <PRODUCT_TYPE_configurable>
        <reference name="head">
            <action method="addItem">
                <type>skin_js</type>
                <name>js/configurable.js</name>
            </action>
        </reference>
    </PRODUCT_TYPE_configurable>

JavaScript – configurable.js

;(function($j){
    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() {
        loadOptions.apply(this);
        var self = this;

        this.settings.each(function(element){
            var attributeId = element.id.replace(/[a-z]*/, '');
            var options = self.getAttributeOptions(attributeId);
            var radioId = attributeId + "-radio";

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

            if(options){
                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);
                    $li.append($radio)
                        .append($span);

                    $optionContainer.append($li);

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

            $j(element).parent().append($optionContainer);
            
            $j(element).css({
                'visibility':'hidden',
                'height': '0px',
                'position': 'absolute'
            });
        });
    }
}(jQuery));
  • 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

<?
Mage::getModel()
Mage::getSingleton()
Mage::getResourceModel()
Mage::helper()
Mage::getResourceSingleton();
Mage::getBlockSingleton();
Mage::getResourceHelper();

About Magento

<?php
Mage::getVersion()
Mage::getVersionInfo();
Mage::getEdition();

Data store

<?php
Mage::register('name', 'value')
Mage::registry('name')
Mage::unregister('name')

Paths & URLs

<?php
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_MEDIA
Mage_Core_Model_Store::URL_TYPE_LINK
Mage_Core_Model_Store::URL_TYPE_DIRECT_LINK
Mage_Core_Model_Store::URL_TYPE_WEB – Store URL
Mage_Core_Model_Store::URL_TYPE_SKIN
Mage_Core_Model_Store::URL_TYPE_JS
Mage_Core_Model_Store::URL_TYPE_MEDIA

<?php
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

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

Redirecting within a controller

<?php
$this->_redirect('module/controller/action')
$this->_redirectUrl('url')
$this->_redirectReferer()
$this->_forward($action, $controller = null, $module = null, array $params);

Alternate redirect method

<?php
$this->getRequest()->initForward()
    ->setControllerName('controllername')
    ->setModuleName('modulename')
    ->setActionName('actionname')
    ->setDispatched(false);

Store

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

Config

<?php
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

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

Exception

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

Logging

<?php
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);

Layout

<?php
Mage::app()->getLayout();

HTTP

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

Product Image Resizing

<?php
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
->getOriginalWidth()
->getOriginalHeight()

$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

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

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


$imageObj = new Varien_Image($_imageUrl);

$imageObj->constrainOnly(true);

$imageObj->keepAspectRatio(true);

$imageObj->keepFrame(false);

$imageObj->resize(140, 140);

$imageObj->save($imageResized);

Products

  • 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.

Outputting Product Attributes

<?php
// For Dropdowns:
$product->getAttributeText($attributeCode);

// For any attribute
$product->getAttribute($attributeCode)->getFrontend()->getValue();

// 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);
Mage::app()->setUpdateMode(false);
Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);

Controller Events

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

Controllers, Actions & Request Object

<?php
// Get the module, controller and action name separated by underscores. Used to attach design XML elements to pages. Eg. catalogsearch_result_index
$request->getFullActionName();

// Get the current action class (outside of the controller)
Mage::app()->getFrontController()->getAction();

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.

<?php
$translate = Mage::getSingleton('core/translate');
$translate->setTranslateInline(false);

Routers

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:

<?php
$request->setModuleName($module);
$request->setControllerName($controller)
$request->setActionName($action)
$request->setControllerModule($realModule)
$request->setDispatched(true);

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;

Reindexing

Reindex everything

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

Reindex a Particular Indexer

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

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();
        $cache->banUse('layout');
    }

Magento Cart Items – Getting Child Product Items

This is how it’s done in the Mage_Checkout_Block_Cart_Item_Renderer_Configurable class. It will return the simple product if available otherwise it will return the product of the cart item.

<?php
public function getChildProduct()
{
    if ($option = $this->getItem()->getOptionByCode('simple_product')) {
        return $option->getProduct();
    }
    return $this->getProduct();
}

Getting Attribute Values

There are a few ways to get get attribute values in Magento:

<?php
$product->getAttributeCode();

$product->getData(attribute_code);
?>

These two methods do the same thing, however if the attribute is a type of select or multiselect then the ID of the value will be returned rather than the text of the attribute.

For these methods, the frontend attribute can be obtained from the product and used to get the attribute value:

<?php
// Get the type of the attribute as dictated by the attribute's *frontend_model* field.
// This will be a Mage_Catalog_Model_Resource_Eav_Attribute model:
$attr = $product->getResource()->getAttribute($attributeName);

// Get the value from the frontend type of the attribute. The frontend type is dictated by the frontend_model column in the eav_attribute table.  By default, this will be Mage_Eav_Model_Entity_Frontend_Default if the frontend_model field is empty.

$val = $attr->getFrontend()->getValue($product);

getAttributeText

Another way to get the value of a select attribute is to use

<?php
$product->getAttributeText(attribute_name);
?>

This will use the attribute’s source_model (typically used in getting all possible values in adminhtml), and looks up the text of the attribute based on the value’s ID (this is set on the product’s data array):

<?php
   public function getAttributeText($attributeCode)
    {
        return $this->getResource()
            ->getAttribute($attributeCode)
                ->getSource()
                    ->getOptionText($this->getData($attributeCode));
    }

Outputting Attributes

Product attributes have various non-standard EAV options to control how they’re output, such as escaping HTML characters. To honour these settings, product attributes should be passed through the catalog/output helper:

<?php
$helper = Mage::helper('catalog/output');

echo $helper->productAttribute($product, $value, 'attribute_name');

Magento Tax Snippets

Getting the Customer’s Address Country For Tax Calculations

The following will perform the following fallbacks:

-The quote’s shipping address
-The customer’s shipping address
-The customer’s billing address
-The default system configuration country

class Namespace_Module_Helper_Data extends Mage_Core_Helper_Abstract {
    const COUNTRY_CODE_GB = "GB";

    public function isDeliveryAddressUk(){
        /** @var Mage_Sales_Model_Quote $quote */
        $quote = Mage::getSingleton('checkout/session')->getQuote();
        $address = $quote->getShippingAddress();

        if($address && $address->getCountryId()){
            return $address->getCountryId() == self::COUNTRY_CODE_GB;
        }

        $cSession = Mage::getSingleton('customer/session');

        if($cSession->isLoggedIn()){
            $customer = $cSession->getCustomer();
            $address = $customer->getDefaultShippingAddress();

            if($address && $address->getCountryId()){
                return $address->getCountryId() == self::COUNTRY_CODE_GB;
            }

            $customer->getDefaultBillingAddress();

            if($address && $address->getCountryId()){
                return $address->getCountryId() == self::COUNTRY_CODE_GB;
            }
        }
        
        return Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_DEFAULT_COUNTRY) == self::COUNTRY_CODE_GB;
    }
}

Checking the Tax Display Type

// Including Tax
if(Mage::helper('tax')->getPriceDisplayType() == Mage_Tax_Model_Config::DISPLAY_TYPE_INCLUDING_TAX){
    ...
}

// Excluding Tax
if(Mage::helper('tax')->getPriceDisplayType() == Mage_Tax_Model_Config::DISPLAY_TYPE_EXCLUDING_TAX){
    ...
}