Tag Archives: Magento Extension

Magento is a perfect solution for E-commerce. There are plenty of features included to meet any requirements and needs of sellers. However, there is a problem when displaying common static pages, such as the About Us and Contact pages, which is called CMS pages in Magento.

There is an option to edit and add them in the Admin panel, but when a user needed to display links on the main navigation bar, there was a problem.

It was impossible to accomplish without code customization, which required a great deal of time and effort. Given how frequently we were asked about this type of development, we decided to create an extension to simplify the process.

The solution we found is based on using a catalogue categories menu, which allows one to render CMS page links and titles instead of category link and category name. To ensure user-friendly management, we reached a solution of adding a new tab to the CMS page edit section following the Meta Data tab. Our custom tab contains a categories tree, like on a product edit page in categories tab, so we can assign a CMS page by clicking checkboxes on the tree nodes. We had to completely rewrite the cms/page model and implement our custom “after save and before delete” logic.

At first sight, it would seem that all we need is to create a reference to cms_page_edit_tabs  block and insert our categories tree inside. However, tabs in the product edit page and cms page edit page are different. We could not use the adminhtml/catalog_product_edit_tab_categories block as a tab in the CMS page edit, because it requires that the tab block calls  the Mage_Adminhtml_Block_Widget_Tab_Interface interface, but the catalogue categories tree doesn’t implement this interface. Therefore, we created a custom adminhtml block and extended the categories tree block. Then, we overrode the methods specific for categories tree view.

We’ve spent about 80 hours on development, but now it will only take a few minutes to install the extension. It creates a new tab in the edit section, where an Admin can simply assign a CMS page to a selected category without using any URL rewrite management or redirects. Moreover, this extension supports multiple stores, which is quite important due to its growing popularity.

If this is something you were looking for, please visit the Itera Research account on Magento Connect

 

Sincerely,

Itera Research team

Sometimes, we want to edit the content of a static block in an easy way, such as how we are able to edit information in the header and footer blocks in the System > Configuration > Design section.
This can be done by adding a custom group with input fields so that we can put our content, titles, and images into separate fields. For example, we can place our custom block group under Footer options (System > Configuration > Design section). Our block configuration panel would look like this:

Let’s create a regular Magento module. The module directories’ structure would be rather simple and consist of only 3 config files, as follows:

app/etc/modules/Ir_Banner.xml
app/code/local/Ir/Banner/etc/config.xml
app/code/local/Ir/Banner/etc/system.xml

The first file (Ir_Banner.xml) tells Magento that an additional module is installed. This file is responsible for module enabling/disabling, module folder location and dependency from other modules. Actually, you can omit the dependency option, as it’s not critical at this point; it affects modules’ loading order only. The file will look like this:

<!-- 
app/etc/modules/Ir_Banner.xml
-->
<?xml version="1.0"?>
<config>
    <modules>
        <Ir_Banner>
            <active>true</active>
            <codePool>local</codePool>
        </Ir_Banner>
    </modules>
</config>

The next file is a module config file and does not have much code either:

<!--
app/code/local/Ir/Banner/etc/config.xml
-->
<?xml version="1.0"?>
<config>
    <modules>
        <Ir_Banner>
            <version>0.0.1</version>
        </Ir_Banner>
    </modules>
</config>

Basically, we are done with the simplest Magento module. At this point, it has no functionality except this:

The last file is system.xml

This file defines what input fields we use, as well as the names and types of these fields, groups, and group properties, such as the label in the group pane heading and what section this group belong to:

<!--
app/code/local/Ir/Banner/etc/system.xml
-->
<?xml version="1.0"?>
<config>
    <sections>
        <design>
            <groups>
                <homepage_banner>
                    <label>Homepage Banner</label>
                    <frontend_type>text</frontend_type>
                    <sort_order>300</sort_order>
                    <show_in_default>1</show_in_default>
                    <show_in_website>1</show_in_website>
                    <show_in_store>1</show_in_store>
                    <fields>
                        <status translate="label">
                            <label>Status</label>
                            <frontend_type>select</frontend_type>
                            <source_model>adminhtml/system_config_source_yesno</source_model>
                            <sort_order>1</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>1</show_in_store>
                        </status>
                        <title translate="label">
                            <label>Banner Title</label>
                            <frontend_type>text</frontend_type>
                            <sort_order>2</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>1</show_in_store>
                        </title>
                        <content translate="label">
                            <label>Content</label>
                            <frontend_type>textarea</frontend_type>
                            <sort_order>30</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>1</show_in_store>
                        </content>
                        <background_image translate="label">
                            <label>Background Image</label>
                            <frontend_type>image</frontend_type>
                            <backend_model>adminhtml/system_config_backend_image</backend_model>
                            <upload_dir config="system/filesystem/media" scope_info="1">homepage_banner_backgrounds</upload_dir>
                            <base_url type="media" scope_info="1">homepage_banner_backgrounds</base_url>
                            <sort_order>1</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>1</show_in_store>
                            <comment>Recommended image size is 300 x 200 px.</comment>
                        </background_image>
                    </fields>
                </homepage_banner>
            </groups>
        </design>
    </sections>
</config>

As you can see, we can add custom fields and groups to existing sections. We can also add custom sections, but this requires an ACL configuration defined in our custom module config.xml, or a special file for this purpose, adminhtml.xml. However, all Magento core modules use adminhtml.xml for ACL definition. The existing section we want to use is design, but the homepage_banner is our custom group. The XML inside <fields></fields> section is a definition of the input fields displayed in the group homepage_banner.

We can also set a label of the group showing options and sort order (our case is after the Footer group). The field definition section (for example, status) has options that define what kind of input we want to use.

  • Label – the text we will see as a label for the input field
  • frontend_type – the input type we use. In our case, the field status is a dropdown selection
  • source_model – the php class that will be used as the data source for dropdown options. The class must implement method toOptionsArray(). In our case, we use one of the default source models, Yesno, but we can use any source model in Magento or create a custom model.
  • sort_order – order in the group
  • show_in_default, show_in_website, show_in_store – visibility options when we have multiple stores

There are many other field options available, but the options we can set depend on the type of the field. For example, the source_model option is only for the select and multiselect frontend_type. The field with the frontend_type image has specific options:

  • upload_dir – relative path to the image in the media folder.
  • homepage_banner_backgrounds is the name of the folder that will be created automatically under media. Therefore, the full path of the image is
  • media/homepage_banner_backgrounds/default/filename.jpg
  • base_url – image url. We can specify this independently from the file path. Therefore, the url to the image in this example is https://domain.dev/media/homepage_banner_backgrounds/default/filename.jpg

The field “Next step” displays our block on the front end. The easiest way to do this is with content directives, which we can insert into any page in CMS > Pages, for example.

<h3>{{config path='design/homepage_banner/title'}}</h3>
<img href="{{store url=''}}/{{config path='design/homepage_banner/background_image'}}”/>

Let’s take a look at the directive config {{config path='design/homepage_banner/title'}} we can see the path property here. Now, let’s take a look at our system.xml file:

<!--
app/code/local/Ir/Banner/etc/system.xml
-->
<?xml version="1.0"?>
<config>
    <sections>
        <design>
            <groups>
                <homepage_banner>
                    ...
                    <fields>
                        ...                        
                        <title translate="label">
                            ...
                        </title>
                        ...
                    </fields>
                </homepage_banner>
            </groups>
        </design>
    </sections>
</config>

As we can see, the path in the config directive reflects the path in the .xml tree like this: path='section_name/group_name/field_name'.

Another way to show the block is in the *.phtml template. We can use the same HTML snippet from above. To get the config values, we can use the standard Magento method:

<h3><?php echo Mage::getStoreConfig('design/homepage_banner/title'); ?></h3>

Sincerely,

Itera Research team

As we already know, we can add custom sections with input fields to the system configuration page in admin by using a system.xml config file in our module. The Magento Adminhtml module provides different types of form fields that we can use, one of which is Form Field Array. This form field allows you to create multiple rows with input fields.

This is a great alternative to admin grids, because when we want to use the grid, it assumes that we have data collection, model, and related database table. A field array is a simple way to store multiple items without using collections. It would be more user-friendly to edit footer links in this way rather than editing links in CMS block. Let’s create a custom module Ir_Menu as an example. The module config files would be like this:

file: app/code/local/Ir/Menu/etc/config.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Ir_Menu>
            <version>0.0.1</version>
        </Ir_Menu>
    </modules>
    <global>
        <blocks>
            <menu>
                <class>Ir_Menu_Block</class>
            </menu>
        </blocks>
    </global>
</config> 

file: app/code/local/Ir/Menu/etc/config.xml

<?xml version="1.0"?>
<config>
    <sections>
        <design>
            <groups>
                <main_menu>
                    <label>Main Menu</label>
                    <frontend_type>text</frontend_type>
                    <sort_order>301</sort_order>
                    <show_in_default>1</show_in_default>
                    <show_in_website>1</show_in_website>
                    <show_in_store>1</show_in_store>
                    <fields>
                        <items translate="label">
                            <label>Menu Items</label>
                            <frontend_model>menu/adminhtml_form_field_items</frontend_model>
                            <backend_model>menu/system_config_backend_items</backend_model>
                            <sort_order>100</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>0</show_in_website>
                            <show_in_store>0</show_in_store>
                        </items>
                    </fields>
                </main_menu>
            </groups>
        </design>
    </sections>
</config>

As we can see in the system.xml config, we have a option,  <frontend_model> which actually defines a view block for the field. Implementation of the Ir_Menu_Block_Adminhtml_Form_Field_Items class would be like this:

file: app/code/local/Ir/Menu/Block/Adminhtml/Form/Field/Items.php

class Ir_Menu_Model_System_Config_Backend_Items extends Mage_Adminhtml_Model_System_Config_Backend_Serialized_Array
{
    /**
     * Process data after load
     */
    protected function _afterLoad()
    {
        //$value = $this->getValue();
        //$this->setValue($value);
        parent::_afterLoad();
    }

    /**
     * Prepare data before save
     */
    protected function _beforeSave()
    {
        //$value = $this->getValue();
        //$this->setValue($value);
        parent::_beforeSave();
    }
}

So we need to extend Mage_Adminhtml_Block_System_Config_Form_Field_Array_Abstract class and override_prepareToRender method where we can add our custom columns. This would be enough when we use only text input fields in all columns. But in our case we want to use drop down select with CMS pages. And in this example we have additional custom method _getPageRenderer. As you can see this method creates another layout block. This block then passed as renderer parameter of the addColumn method. This is the way we can customize field array columns. Next step is implementation. This model responsible for data preparing when we save and load configuration on system > configuration page in admin. Here we can specify default Magento classMage_Adminhtml_Model_System_Config_Backend_Serialized_Array which do the job. But in our example we implement custom model Ir_Menu_Model_System_Config_Backend_Items which extendsMage_Adminhtml_Model_System_Config_Backend_Serialized_Array and override methods _afterLoad and_beforeSave like this:

file: app/code/local/Ir/Menu/Model/System/Config/Backend/Items.php

class Ir_Menu_Model_System_Config_Backend_Items extends
Mage_Adminhtml_Model_System_Config_Backend_Serialized_Array
{
    /**
     * Process data after load
     */
    protected function _afterLoad()
    {
        //$value = $this->getValue();
        //$this->setValue($value);
        parent::_afterLoad();
    }

    /**
     * Prepare data before save
     */
    protected function _beforeSave()
    {
        //$value = $this->getValue();
        //$this->setValue($value);
        parent::_beforeSave();
    }
}

Also, don’t forget to change config.xml so it looks like this:

file: app/code/local/Ir/Menu/etc/config.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Ir_Menu>
            <version>0.0.1</version>
        </Ir_Menu>
    </modules>
    <global>
	  <models>
            <menu>
                <class>Ir_Menu_Model</class>
            </menu>
        </models>
        <blocks>
            <menu>
                <class>Ir_Menu_Block</class>
            </menu>
        </blocks>
    </global>
</config>

As we can see, it just serializes the data and saves it to DB. Therefore, we have the following in the core_config_data table:

Let’s get back to the _getPageRenderer method of Ir_Menu_Block_Adminhtml_Form_Field_Items class:
file: app/code/local/Ir/Menu/Block/Adminhtml/Form/Field/Items.php…

...    
    protected function _getPageRenderer()
    {
        if (!$this->_pageRenderer) {
            $this->_pageRenderer = $this->getLayout()->createBlock(
                'menu/adminhtml_form_field_page', '',
                array('is_render_to_js_template' => true)
            );
            $this->_pageRenderer->setClass('cms_page_select');
            $this->_pageRenderer->setExtraParams('style="width:120px"');
        }
        return $this->_pageRenderer;
    }
...

Since we want to customize the field array column with dropdown selection where we can choose the CMS page, we must implement the 'menu/adminhtml_form_field_page' block class:
file: app/code/local/Ir/Menu/Block/Adminhtml/Form/Field/Page.php

{
    private $_cmsPages;
    protected function _getCmsPages($pageId = null)
    {
        if (is_null($this->_cmsPages)) {
            $this->_cmsPages = array();
            $collection = Mage::getResourceModel('cms/page_collection')->load();
            foreach ($collection as $item) {
                $this->_cmsPages[$item->getIdentifier()] = $item->getTitle();
            }
        }
        if (!is_null($pageId)) {
            return isset($this->_cmsPages[$pageId]) ? $this->_cmsPages[$pageId] : null;
        }
        return $this->_cmsPages;
    }

    public function setInputName($value)
    {
        return $this->setName($value);
    }

    public function _toHtml()
    {
        if (!$this->getOptions()) {
            $this->addOption('', 'not selected');
            foreach ($this->_getCmsPages() as $pageId => $pageLabel) {
                $this->addOption($pageId, addslashes($pageLabel));
            }
        }
        return parent::_toHtml();
    }
} 

All we are doing here is extending one of the default Magento core blocks Mage_Core_Block_Html_Select and overridingthe _toHtml method where we can add selection options, and load a collection of CMS pages. At this point, we should see the following in the Magento admin system > configuration > design > Main Menu:

Finally, we need to render our menu on the front end. This will be done by adding an appropriate block class and its template:
file: app/code/local/Ir/Menu/Block/View.php

class Ir_Menu_Block_View extends Mage_Core_Block_Template
{
    protected $_items;
    public function __construct()
    {
        parent::__construct();
        $this->setTemplate('menu/view.phtml');
    }


    public function getItems()
    {
        if (!$this->_items) {
            $this->_items = unserialize(Mage::getStoreConfig('design/main_menu/items'));
        }
        return $this->_items;
    }
}

Since we have the serialized data stored in our config 'design/main_menu/items', we should unserialize it to an array like this:$this->_items = unserialize(Mage::getStoreConfig('design/main_menu/items')); $this->_items  array will look like this:

All we have to do now is create a menu.phtml template and add a layout update in our custom theme. It could be something like this:
file: app/design/frontend/our_package/our_theme/template/menu/view.phtml

<ul>
    <?php foreach($this->getItems() as $item): ?>
    <li>
       <a href="<?php echo $item['link']; ?>"><?php echo $item['title']; ?></a>
    </li>
    <?php endforeach; ?>
</ul>

file: app/design/frontend/our_package/our_theme/layout/menu.xml

<layout version="0.1.0">
<default>
  <reference name="header">
     <block type="menu/view" name="main_menu"/>
  </reference>        
</default>

And don’t forget to add the following to config.xml if we use our own layout update file:
file: app/code/local/Ir/Menu/etc/config.xml

<?xml version="1.0"?>
<config>
    ...
    <frontend>
       <layout>
            <updates>
                <menu module="Ir_Menu">
                    <file>menu.xml</file>
                </menu>
            </updates>
        </layout>
    </frontend>
</config>

Sincerely, Itera Research Team