13

I've had a few scenarios where I've tried to use the cms handles to update a cms page's layout. For example I was trying to use the cms_index_index handle referencing root and setting the page template. This failed and I had to make this layout update via the admin system directly on the homepage cms page display settings.

I've also tried to add a block to reference left using the cms_page handle. Again this failed and I had to implement the layout update via the admin system.

I've read that you can't assign a root template to cms pages. Is that correct and can anyone explain why?

I also wondered if there was a way to enable cms handles to use the standard references such as left, right, root etc? I seem to be able to reference things like head and content just fine.

2 Answers 2

20

Why changing the root template doesn't work

Both

Mage_Cms_IndexController::indexAction()

and

Mage_Cms_IndexController::viewAction()

which are responsible for displaying the default homepage and a CMS page respectively call a helper:

Mage::helper('cms/page')->renderPage($this, $pageId)

If you jump into the helper (located at app/code/core/Mage/Cms/Helper/Page.php) and follow renderPage() to the protected method _renderPage() you'll see that Magento is checking two times for a root template (Magento CE 1.7.0.2):

if ($page->getRootTemplate()) {
    $handle = ($page->getCustomRootTemplate()
                && $page->getCustomRootTemplate() != 'empty'
                && $inRange) ? $page->getCustomRootTemplate() : $page->getRootTemplate();
    $action->getLayout()->helper('page/layout')->applyHandle($handle);     
}

and

if ($page->getRootTemplate()) {
    $action->getLayout()->helper('page/layout')
        ->applyTemplate($page->getRootTemplate());
}

Both calls happen after layout handles like "cms_page" and the like are processed, so you are out of luck here.

What you can do to change the root template

There is an event cms_page_render which you can use for adding your own XML layout handle on CMS pages. Create your own extension (I'll spare some details here) and configure the event observer in your config.xml:

<?xml version="1.0"?>
<config>
    <modules>
        <Emzee_Cms>
            <version>0.0.1</version>
        </Emzee_Cms>
    </modules>

    <global>
        <events>
            <cms_page_render>
                <observers>
                    <emzee_cms_page_render>
                        <class>emzee_cms/observer</class>
                        <method>cms_page_render</method>
                    </emzee_cms_page_render>
                </observers>
            </cms_page_render>
        </events>
        <models>
            <emzee_cms>
                <class>Emzee_Cms_Model</class>
            </emzee_cms>
        </models>
    </global>
</config>

Add your event observer:

<?php

class Emzee_Cms_Model_Observer
{
    public function cms_page_render(Varien_Event_Observer $observer)
    {
        $action = $observer->getEvent()->getControllerAction();

        $actionName = strtolower($action->getFullActionName());
        $action->getLayout()->getUpdate()
            ->addHandle($actionName . '_after');
        return $this;
    }
}

Finally, add your new layout XML handle (e.g. in your local.xml):

<?xml version="1.0"?>
<layout version="0.1.0">
    <cms_index_index_after>
        <reference name="root">
            <action method="setTemplate"><template>page/1column.phtml</template></action>
        </reference>
    </cms_index_index_after>
</layout>

You can use this method as well to add a cms_page_view_after handle or create page specific handles as cms_page_render passes the $page object to your observer.

Why you can't add a block to the 'reference left'

Are you sure that the template you're using has a left column? This question may sound silly but the default "2 columns with right bar" layout for example only offers a 'content' and a 'right' area. I can add blocks to the right column using cms_page without problems so this could be the problem.

In general, you can only easily add blocks to references and echo them if

  • the chosen root template uses the block you're referencing (see app/design/frontend/base/default/template/page/*.phtml) and
  • the block you're referencing either is of type core/text_list, calls $this->getChildhtml() without arguments or does something else to echo all child blocks.

Without further details, I can't tell you why your blocks are not echo'd in the left or right column.

2
  • Hi Matthias. Thanks for the suggestion of adding an event observer, I'll have a look with one of our devs and see if this works out. This could be the answer! @Alan also made a similar point about having the correct default template set for cms pages and the fact the handles are overriden. Thanks for the info as well, I think I've go that figured out now. Jan 29, 2013 at 13:42
  • Hi. Tested this and it works. Also tested it for page specific handles and it's working, but it's not the nicest solution as I need an identifier for pages that wont be changed. As a test I've used the page identifier $cmsPageId = '_' . str_replace('-', '_', $observer->getEvent()->getPage()->getIdentifier()); If someone changed the URL of the cms page, the handle wont function. Ideally a field for 'page key' would exist on the CMS page in the admin system, then the page url, name etc could be changed and the key could be kept the same. Mar 5, 2013 at 17:38
14

Regarding your "can't add a block using <reference name="left/>, are you sure your CMS page has a block named left? For example, if you consider the default home page that ships with the Magento sample data, it appears to have a block named left.

is that a left column?

However, if you look at the page in the backend, you can see it's set to use the root template

`2 columns with right bar`    

and then in its content area, the left column is added using HTML markup (toggle the WYSIWYG to source view)

<div class="col-left side-col">
<p class="home-callout"><a href="{{store direct_url="apparel/shoes/womens/anashria-womens-premier-leather-sandal.html"}}"><img src="{{skin url='images/ph_callout_left_top.gif'}}" alt="" border="0" /></a></p>
<p class="home-callout"><img src="{{skin url='images/ph_callout_left_rebel.jpg'}}" alt="" border="0" /></p>
{{block type="tag/popular" template="tag/popular.phtml"}}</div>

This directed graph makes it clear there's no block named left to hook into (click for full sized image)

directed graph generated with Commerce Bug

Regarding setting a template, if you look at the source for the "Layout" drop down

<select id="page_root_template" name="root_template" class=" required-entry select">
    <option value="empty">Empty</option>
    <option value="one_column">1 column</option>
    <option value="two_columns_left">2 columns with left bar</option>
    <option value="two_columns_right" selected="selected">2 columns with right bar</option>
    <option value="three_columns">3 columns</option>
</select>

You can see when you're setting this field, the actual value getting saved is something like one_column, two_columns_left, etc. These values corespond to layout handles of the same name.

#File: app/design/frontend/default/modern/layout/page.xml
<page_one_column translate="label">
    <label>All One-Column Layout Pages</label>
    <reference name="root">
        <action method="setTemplate"><template>page/1column.phtml</template></action>
        <!-- Mark root page block that template is applied -->
        <action method="setIsHandle"><applied>1</applied></action>
        <action method="setLayoutCode"><name>one_column</name></action>
    </reference>
</page_one_column>
...
<page_two_columns_left translate="label">
    <label>All Two-Column Layout Pages (Left Column)</label>
    <reference name="root">
        <action method="setTemplate"><template>page/2columns-left.phtml</template></action>
        <!-- Mark root page block that template is applied -->
        <action method="setIsHandle"><applied>1</applied></action>
        <action method="setLayoutCode"><name>two_columns_left</name></action>
    </reference>
</page_two_columns_left>

When Magento is rendering a CMS page, it references the saved values, and adds the appropriate layout handle to the page. While it's tangential to the question, that handle is added here

#File: app/code/core/Mage/Cms/Helper/Page.php
protected function _renderPage(Mage_Core_Controller_Varien_Action  $action, $pageId = null, $renderLayout = true)
{
    //...
    $action->addActionLayoutHandles();        
    if ($page->getRootTemplate()) {
        $handle = ($page->getCustomRootTemplate()
                    && $page->getCustomRootTemplate() != 'empty'
                    && $inRange) ? $page->getCustomRootTemplate() : $page->getRootTemplate();
        $action->getLayout()->helper('page/layout')->applyHandle($handle);
    }  
    //...
}

More importantly though is the order the layout handles are added in

Handles Tab of Commerce Bug

As you can see in the screenshot above, the page_two_columns_right handle is added after the cms_index_index handle. This means if you add layout xml update code to change the template in cms_index_index your code will run, but then the layout update xml code in page_two_columns_right will run after it.

I've always suspected this is by design to ensure the template set in the user interface is always correct. In previous version of Magento the <action method="setIsHandle"><applied>1</applied></action> method call seems to have existed for the same reasons.

So, there's no way to do what you want using pure layout xml code. If you're comfortable with creating custom modules and observer code, then look into the cms_page_render event. This fires right before loadLayoutUpdates is called, and would let you slide an additional handle name in, or remove the existing handle names.

3
  • Thanks Alan, your comments regarding adding blocks to a cms page make complete sense. I usually set templates for different pages using the various xml handles Magento provides. My slightly better understanding of cms pages has made it clearer why I've had issues using references for cms pages. I need to make sure the default handle in my page.xml has the correct layout for cms pages. Seems I've glossed over that handle, because I'm usually overidding it elsewhere. Is that correct? Could I ask how you produced the graph and which tool you're using to view the handles for a specific request? Jan 29, 2013 at 13:37
  • 2
    @MarkWeston The diagrams and handle UI some from Commerce Bug, a commercial debugging tool I created and sell. (see alanstorm.com/find_magento_block_name for details) I'm not sure I understood your question about the handles, but if you're setting a template in the default handle, then the layout set in the admin will still win (i.e. page_two_columns_right will still run later). Also, Re: terminology — you don't override handles, your handles always coexists with the others — its just the order they run in effects the end result. Jan 29, 2013 at 19:28
  • Cheers Alan. I see what you mean about calling the use of a handle to set a template not being an override - good point. Thanks for the info on Commerce Bug. Jan 30, 2013 at 13:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.