Magento Design Output Rendering

In this blog post we will see how magento renders output from blocks and phtml files.
In the previous blog post we saw how magento loads all layout xml files, blocks and phtml files. In this blog post we will see how magento outputs it to the browser

renderLayout()

The function which we call in our controller files to send output is renderLayout(). This function is defined in “Mage_Core_Controller_Varien_Action”

The main code in this function is

        Mage::dispatchEvent('controller_action_layout_render_before');
        Mage::dispatchEvent('controller_action_layout_render_before_'.$this->getFullActionName());

        #ob_implicit_flush();
        $this->getLayout()->setDirectOutput(false);

        $output = $this->getLayout()->getOutput();
        Mage::getSingleton('core/translate_inline')->processResponseBody($output);
        $this->getResponse()->appendBody($output);

The code “setDirectOutput(false)” basically turns on “ob_start()” (output buffering). So because of this direct output to browser is turned off and flushing of output buffer only happens when we manually “echo” the output. Next lets look at the “getOutput()” function in “Mage_Core_More_Layout” class.

      public function getOutput() {
        $out = '';
        if (!empty($this->_output)) {
            foreach ($this->_output as $callback) {
                $out .= $this->getBlock($callback[0])->$callback[1]();
            }
        }

        return $out;
    }

This function does a foreach loop on “$this->_output”, the _output array contains references to blocks which have “output” attribute set in them. e.g

<block type="page/html" name="root" output="toHtml" template="page/3columns.phtml">

so block “root” has output=”toHtml” set in it.
This block is called by the “getOutput()” function, the toHtml() function is called on the “root” block.
The “toHtml()” function is defined in the class “Mage_Page_Block_Abstract” class. This function first checks loaded html in cache, if it exists directly reads from cache else calls the “_toHtml()” function.
The “_toHtml()” is defined in “Mage_Page_Block_Template” class, and this function loads the template file and simply “includes” it. Including the template file causes all other child blocks/templates also to be included and hence the entire html output is generated.

Next in the “renderLayout()” function we have

Mage::getSingleton('core/translate_inline')->processResponseBody($output);
$this->getResponse()->appendBody($output);

The “getResponse()” object is an instance of class “Mage_Core_Controller_Response_Http”

So till this step we have generated all html and set it in the response object.

Next in our “Mage_Core_Controller_Varien_Front” class, the “dispatch()” function the last line which we have is

$this->getResponse()->sendResponse();

This function simply send any headers which were set previously and then outputs (echo) the content.

Magento Unique Blocks

In this blog post we are going to see some unique blocks in magento which are different from all other standard magento blocks.

Most blocks in magento extend the Mage_Core_Block_Template class, but there are few other block which don’t extend this class. Due this reason these blocks don’t have a template file and behave differently.

Mage_Core_Block_Text_List

This block is called a Structural Blocks in magento. This block doesn’t have any template file and has its own custom “_toHtml()” function.
The sole purpose of this block is to simply display output of its child blocks in a sorted order and display it.

    protected function _toHtml()
    {
        $this->setText('');
        foreach ($this->getSortedChildren() as $name) {
            $block = $this->getLayout()->getBlock($name);
            if (!$block) {
                Mage::throwException(Mage::helper('core')->__('Invalid block: %s', $name));
            }
            $this->addText($block->toHtml());
        }
        return parent::_toHtml();
    }

As you can see above it reads all its child blocks in a sorted manner and displays their output.
This block is very useful and is used through magento. Most structural blocks like “content”, “right”, “left” etc are all of type “core/text_list”. This is the reason why we are able to take “reference” of blocks like “content”, “right”, “left” and add child blocks to it, and these block automatically get displayed.

Usage

<block type="core/text_list" name="right" as="right" translate="label">
                <label>Right Column</label>
            </block>

Mage_Page_Block_Html_Wrapper

This is another important block in magento. This block creates a wrapper element and appends all block output inside it. Wrapper basically means creates a >div< by default and put html inside it.

Usage:

               <block type="page/html_wrapper" name="top.container" as="topContainer" translate="label">
                    <label>Page Header</label>
                    <action method="setElementClass"><value>top-container</value></action>
                    <action method="setElementId"><value>top-container</value></action>
                    <action method="setElementTagName"><value>div</value></action>
                </block>

By default the element tag name is ‘div’, but we can use any tag name

Mage_Core_Block_Text

This is another block which is not used very often. The class “core/text_list” extends “core/text” class.
This block has a very simple purpose, we can add any text to it and it will display it.

   <block type='core/text' name='test.text'>
      <action method="addText" translate="text"><text>Hello Text</text></action>
      <action method="addText" translate="text"><text>Before Hello!</text><before>true</before></action>
   </block>

This block is seldom used in magento.

Mage_Core_Block_Flush

This block is same as the ‘core/text_list’ block in its function except it has ” ob_implicit_flush();” function called in its “_toHtml()” function. This basically turns off output buffering which is not good since magento by default use output buffering to render its template files.
I could not find where this block is used in magento right now. Its also mentioned in comments Immediate flush block. To be used only as root

Usage:


Mage_Core_Block_Messages

This block is used in magento to display all error/success/warning messages. The “_toHtml()” function basically reads all error/warning/success from core/session class and display all of them together.

Usage

Mage_Core_Block_Text_Tag

This is a block which can be used to generate any html tag.

Usage:

            <block type='core/text_tag' name='test.tag'>
                <action method='setTagContents'>
                    <text>Tag Content</text>
                </action>
                <action method='setTagName'>
                    <value>a</value>
                </action>
                <action method='setTagParam'>
                    <param>href</param>
                    <value>http://google.com</value>
                </action>
            </block>

We also have other classes “Mage_Core_Block_Text_List_Item” and “Mage_Core_Block_Text_List_Link” which are used for similar purpose.

Mage_Core_Block_Profiler

This blocks as the name suggests is used to display the profiling information.

Magento Design and Layout Initialization

In this blog post we will see in detail how magento initializes theme, layout files, how the fallback method for theme is setup, how layout is compiled, how html output is generated and other similar things.

Package and Layout Initialization

The design package initialization happens when we call the function $this->loadLayout() in our controller. The function “loadLayout()” is defined in “Mage_Core_Controller_Varien_Action” class.
Lets look at step by step how the code flows in the loadLayout() function.

Lets see the important lines below

$this->getLayout()->getUpdate()->addHandle($handles ? $handles : 'default');

“$this->getLayout()” creates an instance of class “Mage_Core_Model_Layout”
and “$this->getLayout()->getUpdate()” creates an instance of “Mage_Core_Model_Layout_Update”
The “addHandle()” function adds the >default< handle to the layout_update object. This object basically maintains an array of all handles added to it.
Next we have

$this->addActionLayoutHandles();

The “addActionLayoutHandles()” adds three handles as shown below

        $update->addHandle('STORE_'.Mage::app()->getStore()->getCode());

        // load theme handle
        $package = Mage::getSingleton('core/design_package');
        $update->addHandle(
            'THEME_'.$package->getArea().'_'.$package->getPackageName().'_'.$package->getTheme('layout')
        );

        // load action handle
        $update->addHandle(strtolower($this->getFullActionName()));

basically “STORE_default”, “THEME_frontend_{{package}}_{{layout}}” and “{{module}}_{{controller}}_{{action}}” handles are added. (replace {{}} with actual values)

Next we have the function

$this->loadLayoutUpdates();

This function basically reads all layout xml files, loads its configuration and then based on the handles added creates config object. We will look into this function in detail later.

$this->generateLayoutXml();

This function mainly removes any blocks which we have specified in the >remove< tag in layout xml files.

$this->generateLayoutBlocks();

This function creates an instance of all block files and sets block hierarchy (i.e parent/child) blocks. This function also reads >reference< tag in layout files and creates reference blocks as well.

Getting the Package Name

Above a function was used getPackageName(), this function is defined in class “Mage_Core_Model_Package”. This function return the package name if defined or else calls the function setPackageName() to find the default package name.

     public function setPackageName($name = '') {
        if (empty($name)) {
            // see, if exceptions for user-agents defined in config
            $customPackage = $this->_checkUserAgentAgainstRegexps('design/package/ua_regexp');
            if ($customPackage) {
                $this->_name = $customPackage;
            } else {
                $this->_name = Mage::getStoreConfig('design/package/name', $this->getStore());
            }
        } else {
            $this->_name = $name;
        }
        // make sure not to crash, if wrong package specified
        if (!$this->designPackageExists($this->_name, $this->getArea())) {
            $this->_name = self::DEFAULT_PACKAGE;
        }
        return $this;
    }

So as you can see the function first checks the “UserAgent Exception list” which we can define while setting up theme in admin. If no exception is found it simply reads the package name from “core_config_data” table.

Initializing Layout XML Files

Above we saw the function “loadLayoutUpdates”, this function calls another function “load()” in “Mage_Core_Model_Layout_Update” class.
This function loads xml for handles from layout xml files.
There is function fetchFileLayoutUpdates() which finds all layout files from themes.

fetchFileLayoutUpdates()
This is the main code written inside the function

           $this->_packageLayout = $this->getFileLayoutUpdatesXml(
                $design->getArea(),
                $design->getPackageName(),
                $design->getTheme('layout'),
                $storeId
            );

in my case i have selected the “rwd” theme in magento so
$design->getArea() => ‘frontend’
$design->getPackageName() => ‘rwd’
$design->getTheme(‘layout’) => ‘default’

The function getFileLayoutUpdatesXml reads the configuration “frontend/layout/updates” from all config.xml file. This is where we specify the name of layout xml for each module.

        $updatesRoot = Mage::app()->getConfig()->getNode($area.'/layout/updates');
        Mage::dispatchEvent('core_layout_update_updates_get_after', array('updates' => $updatesRoot));
        $updates = $updatesRoot->asArray();
        $themeUpdates = Mage::getSingleton('core/design_config')->getNode("$area/$package/$theme/layout/updates");
        if ($themeUpdates && is_array($themeUpdates->asArray())) {
            //array_values() to ensure that theme-specific layouts don't override, but add to module layouts
            $updates = array_merge($updates, array_values($themeUpdates->asArray()));
        }
        $updateFiles[] = 'local.xml';

So above gets the filenames of theme layout files from modules and also loads an additional local.xml file.

TIP: local.xml is located in layout folder inside theme directory and this can be used to theme specific layouts
Next we have

          foreach ($updateFiles as $file) {
            $filename = $design->getLayoutFilename($file, array(
                '_area'    => $area,
                '_package' => $package,
                '_theme'   => $theme
            ));
            if (!is_readable($filename)) {
                continue;
            }
            $fileStr = file_get_contents($filename);
            $fileStr = str_replace($this->_subst['from'], $this->_subst['to'], $fileStr);
            $fileXml = simplexml_load_string($fileStr, $elementClass);
            if (!$fileXml instanceof SimpleXMLElement) {
                continue;
            }
            $layoutStr .= $fileXml->innerXml();
        }
        $layoutXml = simplexml_load_string('<layouts>'.$layoutStr.'</layouts>', $elementClass);

Here for each layout theme file, the function getLayoutFilename is called to get the full path. Inside the “getLayoutFilename” function all theme fallback operations happen so we will see this next.

Theme Fallback

As we know magento implements a fallback method to default/base theme if a certain file is not found in the main theme. Lets see how this is implemented in code.
Above we saw the function “getLayoutFilename()”, this function is defined in the class “Mage_Core_Model_Package”.
Similarly we have function like “getTemplateFilename()”, “getLocaleFileName()”, “getSkinUrl()” which are used to find file name with fallback scheme implemented. All the above function call “getFilename()”

        $result = $this->_fallback(
                $file, $params, $this->_fallback->getFallbackScheme(
                        $params['_area'], $params['_package'], $params['_theme']
                )
        );

“$this->_fallback” is an instance of class “Mage_Core_Model_Design_Fallback”
We we check the function “getFallbackScheme()” this is the main code written

            if ($this->_isInheritanceDefined($area, $package, $theme)) {
                $scheme = $this->_getFallbackScheme($area, $package, $theme);
            } else {
                $scheme = $this->_getLegacyFallbackScheme();
            }

The function “_isInheritanceDefined()” checks the xpath “$area . ‘/’ . $package . ‘/’ . $theme . ‘/parent'” in your
package/theme/etc/theme.xml file
e.g the “rwd/default” theme’s theme.xml contains

<theme>
    <parent />
</theme>

So this has an empty parent, but still the “_isInheritanceDefined” return true. In this case the fallback is directly “base”, it wont check the “default” theme. But in the above theme.xml we can specify a fallback theme if required like

<theme>
    <parent>default/mordern</parent>
</theme>

So this would create a proper chain for fallback.

Magento Translate System

In this blog post we will look into the details of magento translate system.

Basics

Magento translate system is used to setup a single website in multiple languages.
To setup a simple multi language system, we can create either a new store view or a new store or a new website from magento admin. For this go to System -> Manage Stores and create a new store view/store/website. Next go to System -> Configuration -> General -> Locale and set the language you want the new store to be in. We will not cover in detail how to set this up properly as its not relevant to the current blog.

There are two ways using which we can setup translations

1. Using translation csv files
2. Inline Translate Feature

Translate csv file are located in app/locale/en_US by default. You can further add translational files for each language as well. In this folder translation files are setup per module, also for each module in the config.xml file we can define the translation file to use

<frontend>
   <translate>
        <modules>
                <Mage_Catalog>
                    <files>
                        <default>Mage_Catalog.csv</default>
                    </files>
                </Mage_Catalog>
        </modules>
   </translate>
</frontend> 

similarly we can define a different translate file to “adminhtml” area as well.
There is a second location where translate file is located: “app/design/frontend/default/{theme}/locale/en_US/translate.csv”. In this location we can have only a single translate file and not multiple files.

TIP: Translate files are simple key value pair files. Key is the input string and value is output in the desired language.

Inline Translate feature needs to be enabled from “System -> Configuration -> Advanced -> Developer -> Translate Inline”. Here you can turn on translate for admin or frontend. Turning this on allows us to directly edit/save translations from magento web ui itself. These translations are saved in magento database in table “core/translate”

Using Translation In Custom Modules and Themes

We have seen above how to include a custom translation file for our theme and module. To enable translation in our template files we need to use the function __(''); provided by magento. The function “__()” can take in n-number of arguments and works same the as php’s sprintffunction.
e.g

echo __('Having Fun?');
echo __('Having Fun? %s','Ya');
echo __('Having Fun? %s %d','Ya',date('h'));

This function is available in .phtml files, block files, controllers, helpers. So basically it available everywhere except model files.

We can also use translation in our configuration files
e.g

<checkout translate="label" module="checkout">
            <label>Checkout</label>

This says to use the translate file of checkout module to translate the label “Checkout”

Digging Deep

Lets now look at the code level on how magento implements translations.

Files to Look at:
1. Mage_Core_Model_Translate
2. Mage_Core_Model_Locale

Lets first look at the definition of the function “__()”

    public function __()
    {
        $args = func_get_args();
        $expr = new Mage_Core_Model_Translate_Expr(array_shift($args), $this->getModuleName());
        array_unshift($args, $expr);
        return $this->_getApp()->getTranslator()->translate($args);
    }

This function definition is located in files
“Mage_Core_Block_Abstract”,”Mage_Adminhtml_Controller_Action”, “Mage_Core_Controller_Front_Action”, “Mage_Core_Helper_Abstract”
This function ultimately calls the “translate()” function in “Mage_Core_Model_Translate”

There is an “init()” function the class “Mage_Core_Model_Translate” which initializes all translations, so lets look at that function in detail. The function is divided into 3 parts “_loadModuleTranslation” , “_loadThemeTranslation”, “_loadDbTranslation”. The purpose of each method is evident from its name.

1. _loadModuleTranslation():

        foreach ($this->getModulesConfig() as $moduleName=>$info) {
            $info = $info->asArray();
            $this->_loadModuleTranslation($moduleName, $info['files'], $forceReload);
        }
        protected function _loadModuleTranslation($moduleName, $files, $forceReload=false)
        {
             foreach ($files as $file) {
                 $file = $this->_getModuleFilePath($moduleName, $file);
                 $this->_addData($this->_getFileData($file), $moduleName, $forceReload);
             }
             return $this;
        }
        protected function _getModuleFilePath($module, $fileName)
        {
            $file = Mage::getBaseDir('locale');
            $file.= DS.$this->getLocale().DS.$fileName;
            return $file;
         }

The above function reads configuration information for each module from “frontend/translate/module” or “adminhtml/translate/module” depending on the area. Loads the file from the ‘locale’ folder and from the current locale set for the store, and add the translation using “_addData()” function.

TIP: It can be seen here that for each module we can specify multiple files in the “files” tag in config.xml

2. _loadThemeTranslation():

    protected function _loadThemeTranslation($forceReload = false)
    {
        $file = Mage::getDesign()->getLocaleFileName('translate.csv');
        $this->_addData($this->_getFileData($file), false, $forceReload);
        return $this;
    }

In this we can see that translate.csv file is loaded for the current theme.

3. _loadDbTranslation():

    protected function _loadDbTranslation($forceReload = false)
    {
        $arr = $this->getResource()->getTranslationArray(null, $this->getLocale());
        $this->_addData($arr, $this->getConfig(self::CONFIG_KEY_STORE), $forceReload);
        return $this;
    }

In this Inline Translations are loaded from database table “core_translate” and then added to the object using _addData() function.

Next lets look at the “_addData()” function

    protected function _addData($data, $scope, $forceReload=false)
    {
        foreach ($data as $key => $value) {
            if ($key === $value) {
                continue;
            }
            $key    = $this->_prepareDataString($key);
            $value  = $this->_prepareDataString($value);
            if ($scope && isset($this->_dataScope[$key]) && !$forceReload ) {
                /**
                 * Checking previos value
                 */
                $scopeKey = $this->_dataScope[$key] . self::SCOPE_SEPARATOR . $key;
                if (!isset($this->_data[$scopeKey])) {
                    if (isset($this->_data[$key])) {
                        $this->_data[$scopeKey] = $this->_data[$key];
                        /**
                         * Not allow use translation not related to module
                         */
                        if (Mage::getIsDeveloperMode()) {
                            unset($this->_data[$key]);
                        }
                    }
                }
                $scopeKey = $scope . self::SCOPE_SEPARATOR . $key;
                $this->_data[$scopeKey] = $value;
            }
            else {
                $this->_data[$key]     = $value;
                $this->_dataScope[$key]= $scope;
            }
        }
        return $this;
    }

The add data function store data in two arrays “_data” and “_dataScope”. $scope is an important variable in this function
$scope => “frontend”, “adminhtml” in case of module translate files
$scope => false in case of theme translate files
$scope => store key in case of database translate files

Its important to see how data is stored in the “$this->_data” array. To understand lets assume an translate key,value pair
“Add To Cart”,”Add To Basket”
So first this is stored as
$this->_data[‘Add To Cart’] = ‘Add To Basket';

But if this same translate key is found in another module file then in that case
$this->_data[‘module1::Add To Cart’] = ‘Add To Basket';
$this->_data[‘module2::Add To Cart’] = ‘Add To Basket Module2′;
$this->_data[‘Add To Cart’] = ‘Add To Basket Module';

and if developer module is turned on non module translate is removed which means, $this->_data[‘Add To Cart’] is removed.

The final important function which is actually used to translate the string is

    protected function _getTranslatedString($text, $code)
    {
        $translated = '';
        if (array_key_exists($code, $this->getData())) {
            $translated = $this->_data[$code];
        }
        elseif (array_key_exists($text, $this->getData())) {
            $translated = $this->_data[$text];
        }
        else {
            $translated = $text;
        }
        return $translated;
    }

As its easy to see here, first translate string is search according to code (code here is module::text). If code is not found then it searches according to text.

Magento Locale Class

There is another important class which we need to look at “Mage_Core_Model_Locale”. This is class which is used to get all locale based information like language codes, timezone information, currency etc and it gets its data from “Zend_Locale” class.

“Mage_Core_Model_Locale_Config” this class contains all the locale and currency you see in admin system configuration.

There are two important function in this “emulate” and “revert”

    public function emulate($storeId)
    {
        if ($storeId) {
            $this->_emulatedLocales[] = clone $this->getLocale();
            $this->_locale = new Zend_Locale(Mage::getStoreConfig(self::XML_PATH_DEFAULT_LOCALE, $storeId));
            $this->_localeCode = $this->_locale->toString();
            Mage::getSingleton('core/translate')->setLocale($this->_locale)->init('frontend', true);
        }
        else {
            $this->_emulatedLocales[] = false;
        }
    }

In this function we can pass a store and it will load all translate information for that particular locale. As we can see it reinitialize the entire ‘core/translate’ object with new translate information as per store locale.

    public function revert()
    {
        if ($locale = array_pop($this->_emulatedLocales)) {
            $this->_locale = $locale;
            $this->_localeCode = $this->_locale->toString();
            Mage::getSingleton('core/translate')->setLocale($this->_locale)->init('adminhtml', true);
        }
    }

The revert function undo’s the effect of emulate. It loads the previous locale and its translation.
But it can be seen here that “emulate()” function loads the ‘frontend’ area while revert loads the ‘adminhtml’ area.
Not sure if this a bug by magento or this is how it was intended because i was not able to figure out the reason for this.