Monday 8 August 2011

Passing Paramters to Magento CMS Static Blocks

If we want our magento controllers to be able to load a static cms block (using AJAX for instance) and render the variable values that it can pass when we load the block then we can, Load the block and declare an array of variables; $block = $this->getLayout()->createBlock('cms/block')->setBlockId('block_id'); $variables = array(); Declare the variables we wish to pass (that we might have got from the params passed on to controller)... so we set the variable name and value as, $variables['variable_name'] = $value; Now when we return the html or render it we need to pass it through a filter, which has various directives method to run the codes in the parenthesis, {{ }}, within a static block, Therefore we render it now as, $filter = Mage::getModel('core/email_template_filter'); //or any of the $filter->setVariables($variables); echo $filter->filter($block->toHtml()); So now in the cms block anything with {{ var variable_name }} will be parsed. Therefore we can send variables and parameters to magento cms static block in this fashion even when we are rendering it using AJAX.

Wednesday 23 March 2011

Customer Address Format (by country) in Magento

There are easier ways to format Customer addresses in Magento than to dig in code and code in line... Default If you want to add a default address format regardless of address country then you simply need to add codes to your config.xml as follows: <config> <default> <customer> <address_templates> <text><![CDATA[{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}} {{depend company}}{{var company}}{{/depend}} {{if street1}}{{var street1}} {{/if}} {{depend street2}}{{var street2}}{{/depend}} {{depend street3}}{{var street3}}{{/depend}} {{depend street4}}{{var street4}}{{/depend}} {{if city}}{{var city}}, {{/if}}{{if region}}{{var region}}, {{/if}}{{if postcode}}{{var postcode}}{{/if}} {{var country}} T: {{var telephone}} {{depend fax}}F: {{var fax}}{{/depend}}]]></text> <oneline><![CDATA[{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}}, {{var street}}, {{var city}}, {{var region}} {{var postcode}}, {{var country}}]]></oneline> <html><![CDATA[{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}}<br/> {{depend company}}{{var company}}<br />{{/depend}} {{if street1}}{{var street1}}<br />{{/if}} {{depend street2}}{{var street2}}<br />{{/depend}} {{depend street3}}{{var street3}}<br />{{/depend}} {{depend street4}}{{var street4}}<br />{{/depend}} {{if city}}{{var city}}, {{/if}}{{if region}}{{var region}}, {{/if}}{{if postcode}}{{var postcode}}{{/if}}<br/> {{var country}}<br/> {{depend telephone}}T: {{var telephone}}{{/depend}} {{depend fax}}<br/>F: {{var fax}}{{/depend}}]]></html> <pdf><![CDATA[{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}}| {{depend company}}{{var company}}|{{/depend}} {{if street1}}{{var street1}} {{/if}} {{depend street2}}{{var street2}}|{{/depend}} {{depend street3}}{{var street3}}|{{/depend}} {{depend street4}}{{var street4}}|{{/depend}} {{if city}}{{var city}}, {{/if}}{{if region}}{{var region}}, {{/if}}{{if postcode}}{{var postcode}}{{/if}}| {{var country}}| {{depend telephone}}T: {{var telephone}}{{/depend}}| {{depend fax}}<br/>F: {{var fax}}{{/depend}}|]]></pdf> <js_template><![CDATA[#{prefix} #{firstname} #{middlename} #{lastname} #{suffix}<br/>#{company}<br/>#{street0}<br/>#{street1}<br/>#{street2}<br/>#{street3}<br/>#{city}, #{region}, #{postcode}<br/>#{country_id}<br/>T: #{telephone}<br/>F: #{fax}]]></js_template> </address_templates> </customer> </default> </config> By Country There is an easy way to add the above formatting based on each country, (for instance addresses in France and Germany needs to have postcodes before city names). All you need to do is to add in DB table 'directory_country_format' the country id (2 digit country ID), type (html/text/pdf) and the format text. An Example is given below: INSERT INTO `directory_country_format` (`country_id`, `type`, `format`) VALUES ('FR', 'html', '{{depend company}}{{var company}} {{/depend}}\n{{var firstname}} {{var lastname}} \n{{var street1}} \n{{depend street2}}{{var street2}} {{/depend}}\n{{depend postcode}}{{var postcode}}{{/depend}}{{depend city}} {{var city}}{{/depend}}{{depend region}}, {{var region}}{{/depend}} \n{{var country}} \n{{depend telephone}}Tel : {{var telephone}}{{/depend}}\n{{depend fax}} Fax : {{var fax}}{{/depend}}'), ('FR', 'text', '{{depend company}}{{var company}}\n{{/depend}}{{var firstname}} {{var lastname}}\n{{var street1}}{{depend street2}}{{var street2}}\n{{/depend}}\n{{depend postcode}}{{var postcode}}{{/depend}}{{depend city}} {{var city}}{{/depend}}{{depend region}}, {{var region}}{{/depend}}\n{{var country}}{{depend telephone}}\nTel : {{var telephone}}{{/depend}}{{depend fax}}\nFax : {{var fax}}{{/depend}}'), ('FR', 'pdf', '{{depend company}}{{var company}}|\n{{/depend}}{{var firstname}} {{var lastname}}|\n{{var street1}}|{{depend street2}}{{var street2}}|\n{{/depend}}\n{{depend postcode}}{{var postcode}}{{/depend}}{{depend city}} {{var city}}{{/depend}}{{depend region}}, {{var region}}{{/depend}}|\n{{var country}}{{depend telephone}}|\nTel : {{var telephone}}{{/depend}}{{depend fax}}|\nFax : {{var fax}}{{/depend}}|'); Special cases / formatting In some cases we might want to do special formatting to certain data on address object so that they are printed out in the format the way we want (for instance we might want to print the country name or the last name in uppercase). That is achievable using observers on event 'customer_address_format'. And your observer can be like the example below: class MyModule_Model_Countryformat { public function countryFormat($observer) { $event = $observer->getEvent(); $address = $event->getAddress(); $type = $event->getType(); if($address->getData('country_id') == 'FR'){ $lastname = strtoupper($address->getData('lastname')); $address->setData('lastname', $lastname); } } }
UPDATE
You can even add your own address format tags other than the defined html, pdf etc... in order to do so you need to add the following in your config.xml of your module, where mytag is your new tag: <config> <global> <customer> <address> <formats> <mytag template="title" module="customer"> <title>My custom address template</title> </mytag> </formats> </address> </customer> </global> </config> and then can simply add your format in the same config file <config> <default> <customer> <address_templates> <mytag><![CDATA[{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}} {{depend company}}{{var company}}{{/depend}} {{if street1}}{{var street1}} {{/if}} {{depend street2}}{{var street2}}{{/depend}} {{depend street3}}{{var street3}}{{/depend}} {{depend street4}}{{var street4}}{{/depend}} {{if city}}{{var city}}, {{/if}}{{if region}}{{var region}}, {{/if}}{{if postcode}}{{var postcode}}{{/if}} {{var country}} T: {{var telephone}} {{depend fax}}F: {{var fax}}{{/depend}}]]></mytag> </address_templates> </customer> </default> </config>

Friday 18 February 2011

Magento Enterprise Full Page Caching

We will discuss here how to setup various magento full page cache options for your site. How it works

Basically, magento has defined 3 processors 1) for product pages, 2) catalog lists and 3) for rest of the CMS pages. In order to add full page caching to any page in magento you need to do have the following tags in your module's config.xml in etc... **Note: Magento Enterprise by default has configured all the CMS pages, Product pages and catalog lists for Caching.

<frontend> <cache> <requests> <!-- Supported definitions: <module_front_name>processor_model</module_front_name> - all module urls<br /> <module_front_name><controller>processor_model</controller></module_front_name> - all module controller actions<br /> <module_front_name><controller><action>processor_model</action></controller></module_front_name> - specific action<br /> --> <mymodule_mycontroller_myaction>enterprise_pagecache/processor_default</mymodule_mycontroller_myaction> </requests> </cache> </frontend>

This could be your dashboard for instance which magento does not cache by default in which case you need to add the tags:

<customer_account>enterprise_pagecache/processor_default</customer_account>

But this configuration would cache the whole page regardless, and you would not want that on customer dashboards where you want to see only a valid customer information and the information might also need to update based on some events.

So we now need to have a cache.xml file which tells us which blocks on the page needs to have special behavioral pattern than other blocks on the page. For instance the recent order block in my dashboard would need to update each time an order is placed, where else the address and customer names should only belong to the current logged in customer and not a cached data from another customer. Some of the blocks may on the other hand be used by different customers.

<?xml version="1.0" encoding="UTF-8"?> <config> <placeholders> <myblock_someaction> <block>mymodule/myblock</block> <name>blockname</name> <placeholder>MY_BLOCK</placeholder> <container>MyModule_Model_MyModel</container> <cache_lifetime>84600</cache_lifetime> </myblock_someaction> </placeholders> </config>

Notice in the above tags, the tagname should be any unique cache tagname, in our case its 'myblock_someaction'. The block tag and the name tag should have the block name and the name as it appears in the layout in design. The placeholder tag should have any unique string. The container needs to contain the model which actually defines the behavior of the block cache. And finally, the cache_lifetime is the time till the cache will be valid. Now, all that matters is the model which defines the behavior of the cache and we hope to look it in details.

Magento Enterprise already handles some of the blocks like the cart_container on the header, the catalog navigation and some other blocks for reporting. Let us know try to define three type of cache model 1) Which is customer specific 2) Is some template specific 3) The block that we always want to refresh.

For Customer specific model we would mainly need to override two abstract methods. 1) _getCacheId() and _renderBlock(). An Example is shown below:

class MyModule_Model_MyModel extends Enterprise_PageCache_Model_Container_Abstract { protected function _getCacheId() { return 'CUSTOMER_STATE_' . md5($this->_placeholder->getAttribute('cache_id') . $this->_getCookieValue(Enterprise_PageCache_Model_Cookie::COOKIE_CUSTOMER, '')); } protected function _renderBlock() { $block = $this->_placeholder->getAttribute('block'); $template = $this->_placeholder->getAttribute('template'); $block = new $block; $block->setTemplate($template); $block->setLayout(Mage::app()->getLayout()); return $block->toHtml(); } }

The above code would allow Mage to have browser cookie for the customer and it would use the cookie value to generate an Id. If the customer logs out and logs in as another customer the cookie would refresh and the cache will be invalidated, and so the block will be newly generated for the customer.

Now, we will look into a template specific caching model.

class MyModule_Model_MyModel extends Enterprise_PageCache_Model_Container_Abstract { protected function _getCacheId() { return 'TEMPLATE_' . md5($this->_placeholder->getAttribute('template').$this->_placeholder->getAttribute('cache_id')); } }

Notice that the above does not requires a renderer. This is because we are expecting the page to be there always and we do not expect that it would get invalidated based on some events. This kind of model would help us have template specific caching of a shared block. Finally, we have the ever refreshing block or the ever self invalidating block.

class MyModule_Model_MyModel extends Enterprise_PageCache_Model_Container_Abstract { protected function _getCacheId() { return 'CONSTANT_STRING_' . md5($this->_placeholder->getAttribute('cache_id')); } protected function _renderBlock() { $block = $this->_placeholder->getAttribute('block'); $template = $this->_placeholder->getAttribute('template'); $block = new $block; $block->setTemplate($template); $block->setLayout(Mage::app()->getLayout()); return $block->toHtml(); } protected function _saveCache($data, $id, $tags = array(), $lifetime = null) { return false; } }

Notice in the above we added / overridden _saveCache(...) and it basically never saves. The Id should be return values based on your requirements. And because it always invalidates you would need _renderBlock() funtion.

Finally, here is a tiny little but very useful trick... Because we can never call mage functionalities from these models (yes we cannot call helpers or do logs, AFAIK). We can slip in values to these blocks using a useful mage technique. Suppose we want some dynamic ID based on some block requirements how do we do it in _getCacheId()... remember we cannot even call session or registry.

In order to achieve that you would need a function in your block (the one you mentioned in cache.xml -> block), public function getCacheKeyInfo(). Here is an example below.

public function getCacheKeyInfo() { return $cacheId = array('my_id' => '1234'); }

Now, in the model you can use this as :

class MyModule_Model_MyModel extends Enterprise_PageCache_Model_Container_Abstract { protected function _getCacheId() { return 'SOME_BLOCK_' . md5($this->_placeholder->getAttribute('my_id').$this->_placeholder->getAttribute('cache_id')); } }

Good Luck with your caching!