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!

Sunday 22 August 2010

Moving Data around in Magento

Controller to view Like we have seen previously that blocks are separate from the main controller which contains all the data for the view. This helps to reduce coupling of modules and sub modules. Therefore if we supply the block with values it is directly available in the view. Following is an example code snippet on how sending data from main controller to templates / blocks works: $layout = $this->getLayout(); $block = $layout->getBlock('b'); <-- block name b usually given as <modulename> / <block name> $block->setData('variable_name', $value); $block->setData('variable_2', $value2); and so on... and from the template or block simply $this->getData('variable_name'); helps fetch the value. Global If you like to set data which should be available from any module and any module components like model / view / controllers then simply set the data using Magento register / registry as follows: Set: Mage::register('variable_name', 'data'); Get: Mage::registry('variable_name'); Session To move data around session: Get variable: Mage::getSingleton('core/session')->getData('variable_name'); Set variable: Mage::getSingleton('core/session')->setData('variable_name', 'value'); Set variable: Mage::getSingleton('core/session')->setData('variable_name', $value); Parameter Passing Like we always need to send data from views (from some form) back to some controllers for processing or saving data we always need Get or Set and in Magento its simpler to do: Get / Post Data from form(array): $params = $this->getRequest()->getParams(); Data: $params['param_name'];

Monday 16 August 2010

Running a DB Query

If you want to run a raw query from your magento module it is possible using the following codes: Open db connections
Read connection : $db = mage::getSingleton('core/resource')->getConnection('core_read'); Write connection : $db = mage::getSingleton('core/resource')->getConnection('core_write'); Execute your query given in $query
Run Query: $db->query($query); Finally fetch results:
Fetch Results (array): $db->fetch();

Saturday 14 August 2010

Resources to start-off

The following is a list of resources for starting off with Magento
  • The first is a very good start up tutorial by Alan storm.
  • Second most important is Magento Class Documentations (unfortunately the Magento site documentation is not that helpful) but I find this site useful.
  • In order to create (simple) modules for start, using this online script is helpful and useful.
  • Or Download Module Creator to create modules from your Magento installation
  • If you are confused about how to install extensions:
    1. Get to the Magento Connect site to get your extension. You will be given an extension key.
    2. Goto Magento http://<yoursiteshost>/downloader and paste your key... that should do!

Magento Architecture

Magento's architecture does follows the well known MVC architecture but it actually does have some of its own additions which do help large scale web developments. Like we all know MVCs (Model, View and Controller) architecture is where you have a set of modules which comes with Models, Views and Controllers to split up your codes and make code management easier and simpler. In the conventional MVC, one would request the controller for a service and the controller would use models to get processed data and put forward the data to the view to give user the response and take another request which would be carried out in the same fashion. The conventional MVC works as illustrated below. Magento's architecture has added a lot more sub blocks to the above MVC architecture in order to handle bigger e-commerce system which can handle multiple sites / stores from the same back-end. Magento Architecture Views itself is broken into 3 parts, the model into 2 and you have controllers and helpers where helpers are module specific. If you are thinking that why helpers are module specific then you should know that Magento helpers and in fact all the models and controllers extends Magento core controllers, models and helpers so the common features are there in the super class whereas you can also add module specific features which you need not share to make big helper classes. To start off lets see the image below. We can see that Views have been split into three parts. The templates are the plainly html codes usually saved as phtmls with php tags to prints data and do some basic loops and some javascript calls like our usual view would look like. Next comes blocks which is a new concept to MVC. Blocks are simply used to lower the burden on central controller and make different views in a module more independent. This is important these days and any websites these days stands on a number of different blocks and some blocks may be AJAX loaded and provide different services. Therefore, Views have their own controllers to ask or request processed data from Models and provide graphical 'view' through templates. Blocks holds all the data and functions that can be called from the view template and Block can have nested blocks. Therefore, our website will have a root block, a header block, a navigation block and contents and footer block therefore they can be nested like that. Now, we might wonder what is the role of the central controller if every view comes with their own controller which can interact with models and helpers. That is where layout comes into play actually. In most of the definitions I read previously they say that in order to say which blocks goes with which template and in order to define which block is nested in which we need the layout. This is true but there is a little bit more to it. Layouts can only be called by a central controller name, therefore the central controller has the layout which defines the sub controllers (blocks) and the templates it contains. The central controller along with the helper provide service to the overall block set while the individual blocks provide service in email. Layout therefore simply provides a way to tell which is the super block and which are nested and how they are nested. Finally, Models contains Models ofcourse and collections. Models works as a service it provides functionalities for various business calculations and data processing, whereas, the collection is used to provide functions to retrieve the Data.