7 Custom Magento System Configuration

piaoling  2011-05-03 15:20:10

One of the more powerful parts of the Magento eCommerce System is the Admin’s System Config section. As a developer, it will allow you to quickly and easily setup forms elements that allow your end-users to configure their Magento System and your custom modules.

Like a lot of things in Magento, it can be intimidating the first time you attempt to setup a new configuration section from scratch, but once you’ve done it once the power of (nearly) code-less forms will become addictive. Let’s get started.

We’ll be building off the module we created in the Magento Controller Dispatch and Hello World article, although any empty module should suffice. The impatient can download the complete module for this article here.

The following is part of a longer series about Magento aimed at developers familiar with PHP MVC development. While each article can be read stand alone, each article does build on concepts and code covered in previous articles. If you’re confused, be sure to catch up on the older stuff first.

Adding A System Config File

The first thing we need to do is add a system configuration file to our module. This file is separate from config.xml, and can be found at the following location

app/code/local/Alanstormdotcom/Helloworld/etc/system.xml

While similar to the global config, the system configuration information is stored separately. If you need to view the system config file, it exists as it’s own entity. You can view the entire system config by executing the following PHP code from any controller action

//header('Content-Type: text/xml');         
header('Content-Type: text/plain');         
echo $config = Mage::getConfig()
->loadModulesConfiguration('system.xml')        
->getNode()
->asXML();          
exit;

The loadModulesConfiguration method will look in each configured module’s etc folder for a file with the passed in name (in this case, system.xml). Magento has a number of other configuration files (api.xml, wsdl.xml, wsdl2.xml, convert.xml, compilation.xml, install.xml), and as a module developer you can leverage this functionality to create your own.

Adding a new Tab

The first thing we’re going to do is a add a custom “Tab” to the System Configuration. Tabs are the navigation headers down the left hand side of the Admin in System->Configuration. The default tabs are General, Catalog, Customers, Sales, Services, and Advanced.

Let’s create a new one named “Hello Config”. Create a new system config file and add the following

Location: app/code/local/Alanstormdotcom/Helloworld/etc/system.xml
<config>
    <tabs>
        <helloconfig translate="label" module="helloworld">
            <label>Hello Config</label>
            <sort_order>99999</sort_order>
        </helloconfig>
    </tabs> 
</config>

A few explanations are in order. The name of the <helloconfig /> node is arbitrary, but should be unique among Tabs. It will serve as an identifier for your tab which we’ll use later in the config.

The module="helloworld" attribute identifies which module this tab “belongs” to, <label> determines the name that will be used for your tab, and <sort_order> determines where the tab shows up in relation to the other tabs in the left hand navigation.

With this in place, head over to the System -> Config section. One of two things should happen

  1. The page will load as expected, but without your new tab

  2. You’ll get an error something like the following:
    Fatal error: Class 'Mage_Helloworld_Helper_Data' not found in

A Brief Interlude for Helper Classes

Like a lot of popular PHP MVC systems, Magento has Helper classes, which are used for a variety of tasks that don’t fit neatly into Model, View, or Controller. Helper classes are one of the abstracted grouped class names, meaning system users can override default classes and module developers need to add a section to their config.xml to specify the base class name for Helpers.

A lot of the Magento System code assumes a module has a default Helper class. If you got the exception mentioned above, it’s because your Helloworld module didn’t have this default Helper class and the System was trying to use it. Let’s add one now.

First, we need to add a section the to module’s main config.xml file (NOT the system config)

File: app/code/local/Alanstormdotcom/Helloworld/etc/config.xml

<!-- ... -->
<global>
    <!-- ... -->
    <helpers>
        <helloworld>
            <class>Alanstormdotcom_Helloworld_Helper</class>
        </helloworld>
    </helpers>  
    <!-- ... -->
</global>
<!-- ... -->    

If you’ve spent anytime in the Magento config this should be straight forward. The <helloworld /> tag should be named after your module, and the <class /> tag should contain the base name of all your Helper classes, named with the Magento standard convention

Packagename_Modulename_Helper

Helper’s are loaded with the global Mage object’s static helper method. The following call (assuming the above config file)

Mage::helper('helloworld/foo');

would load the following class

app/code/local/Alanstormdotcom/Helloworld/Helper/Foo.php
class Alanstormdotcom_Helloworld_Helper_Foo

Magento also has the concept of a default Helper for a module. If you provide the Helper class with only the module name

Mage::helper('helloworld');

it will look for a data Helper located at

app/code/local/Alanstormdotcom/Helloworld/Helper/Data.php
class Alanstormdotcom_Helloworld_Helper_Dara

That means the following two calls are equivalent.

Mage::helper('helloworld');
Mage::helper('helloworld/data');

Finally, we need to add the actual Helper class. Add the following file, and you’ll be good to go.

File: app/code/local/Alanstormdotcom/Helloworld/Helper/Data.php
class Alanstormdotcom_Helloworld_Helper_Data extends Mage_Core_Helper_Abstract
{
}   

With all this done, clear your Magento cache and reload the System admin. Your error message should be gone, but your new tab is still missing.

Note: If you’re curious about the kind of things a helper can do for you, checkout the Mage_Core_Helper_Abstract class for a list of useful methods that all helpers will have.

Adding A New Section

The Helper interlude out of the way, our next step is figuring out why our configured Tab isn’t showing up. Each Tab has a number of sections. For example, the Advanced tab has (by default) an Admin, System, Advanced, and Developer section.

If a Tab is configured with no sections, it won’t show up. Let’s fix this by adding a <section /> node to our system config

Location: app/code/local/Alanstormdotcom/Helloworld/etc/system.xml
<config>
    <tabs>
        <helloconfig translate="label" module="helloworld">
            <label>Hello Config</label>
            <sort_order>99999</sort_order>
        </helloconfig>
    </tabs> 
    <sections>
        <helloworld_options translate="label" module="helloworld">
            <label>Hello World Config Options</label>
            <tab>helloconfig</tab>
            <frontend_type>text</frontend_type>
            <sort_order>1000</sort_order>
            <show_in_default>1</show_in_default>
            <show_in_website>1</show_in_website>
            <show_in_store>1</show_in_store>                    
        </helloworld_options>
    </sections>     
</config>

There’s some familiar nodes in this new configuration section, as well as a few new faces.

What is a <helloworld_options/>?

Similar to the <helloconfig /> tag above, this in a artibrary name that’s used to identify your new section.

What is a <label />?

A label defines the display value used in the HTML interface for your new section.

What is a <tab />?

This identifies which Tab your new section should be grouped under. We want our section to show up under our new helloconfig Tab. The helloconfig name comes from the tag used to create the Tab (<helloconfig/>)

What is a <frontend_type />?

This one it tricky. <frontend_type /> has meaning in other sections of the configuration (see below), but it doesn’t appear to do anything here. However, sections in the Core modules use this tag, so it’s best to follow the convention, even when you’re not sure what it does.

What is a <sort_order />?

Again, <sort_order /> determines where this sections shows up vertically compared to other sections in the Tab.

What is a <show_in_default />, <show_in_website />, <show_in_store />?

These are boolean config options, with a valid value of 1 or 0. They determine the level of configuration score/granularity this section has.

With our section configured, Let’s head over to System -> Config again (reloading the Admin page often won’t be enough). You should now see your section and tab near the bottom of the left hand navigation. You can add more sections by adding additional nodes to the <sections /> node.

Access Control

If you click your new section link you’ll be disappointed by the results. A blank admin page will load, and the entire left hand navigation will vanish. That’s because the Adminhtml application can’t find an entry for our new section in the Access Control List (ACL).

ACL is a topic all its own, but I’ll try to explain enough here so this doesn’t seem like total magic. This section is optional, and if you’re not interested skip to the end for the magic XML to paste into your config.xml

There are certain resources (where resource is a loosely defined term) that require a user to be authenticated before using them. Resource here is an abstracted term. It might be a page in the admin, or it might be access to a certain feature. The Magento team decided that System Config sections should have ACL protection.

Resources are defined via URI’s. For example, the “web” config section (under the General tab), if defined with a URI of

admin/system/config/web

Our helloworld_options section would have a URI of

admin/system/config/helloworld_options

The Admin application (often called Adminhtml) is built using the same framework as the store application (the store application is often called the frontend application). In Adminhtml action controllers, whenever a user needs to access a resource protected by ACL, the Adminhtml developer must

  1. Derive a URI for whatever resource the end-user is trying to access
  2. Check that URI against the ACL system, which will determine if the
    logged in user has the right privileges that particular resource
  3. If the user does have the correct privileges, proceed. If they don’t, boot them
    or do something appropriate (like stop rendering the navigation and content areas)

For those interested, this is done for the System Config sections in the _isSectionAllowed method in the following controller

app/code/core/Mage/Adminhtml/controllers/System/ConfigController.php

If you go to System -> Permissions -> Roles, add click the Add a new role button, you can see a graphical tree representation of all the Role Resources defined in your Magento install.

Adding an ACL role

That out of the way, we need to define an ACL resource for our new section. You only need to do this if you’re adding a new section. If you’re adding config options to an existing section you don’t need to touch ACL.

In your module’s config.xml, add the following section

File: app/code/local/Alanstormdotcom/Helloworld/etc/config.xml  
<config>    
    <!-- ... -->
    <adminhtml>
        <acl>
            <resources>
                <admin>
                    <children>
                        <system>
                            <children>
                                <config>
                                    <children>
                                        <helloworld_options>
                                            <title>Store Hello World Module Section</title>
                                        </helloworld_options>
                                    </children>
                                </config>
                            </children>
                        </system>
                    </children>
                </admin>
            </resources>
        </acl>
    </adminhtml>
    <!-- ... -->
</config>

Yes, that’s a mouthful. Let’s break it down a bit. First off, all defined resources are contained in the following node structure.

<adminhtml>
    <acl>
        <resources>
        </resource>
    </acl>
</adminhtml>

Within resource, each descending node represents a URI portion. So, this far down

<admin>
    <children>
        <system>
            <children>

gives us a URI of

admin/system

If you follow that all the way down, you get to the node for our config

<helloworld_options>
    <title>Store Hello World Module Section</title>
</helloworld_options>

Title is what will show up in the Permissions admin.

With this in your config and your Magento cache cleared, you should now be able to view your config section. You may need to log out of the application and back into to see it. The Admin has some additional caching beyond the standard Magento cache that I haven’t been able to track down. Ig you log out, log back in, and navigate to your section you should now see a blank config page titled “Hello World Config Options”.

Note: For some reason Magento strips the <adminhtml /> section of the config from the config object. This means you can’t use the Configviewer to check your work here. I’m investigating where (if anywhere) the <adminhtml /> section gets stored.

Adding Groups

So, we now have a blank config section. Our next step is adding a group.

Groups are used to group together different configuration options, and are displayed in the Magento admin with a pop-open widget. For example, in a stock install the Advanced section has a single group named “Disable modules output”. Let’s create a group named “messages” by adding a <groups /> node to our config, nested within the <sections> node

Location: app/code/local/Alanstormdotcom/Helloworld/etc/system.xml
<config>
    <tabs>
        <helloconfig translate="label" module="helloworld">
            <label>Hello Config</label>
            <sort_order>99999</sort_order>
        </helloconfig>
    </tabs> 
    <sections>
        <helloworld_options translate="label" module="helloworld">
            <label>Hello World Config Options</label>
            <tab>helloconfig</tab>
            <frontend_type>text</frontend_type>
            <sort_order>1000</sort_order>
            <show_in_default>1</show_in_default>
            <show_in_website>1</show_in_website>
            <show_in_store>1</show_in_store>
            <groups>
                <messages translate="label">
                    <label>Demo Of Config Fields</label>
                    <frontend_type>text</frontend_type>
                    <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>                
                </messages>
            </groups>
        </helloworld_options>
    </sections>     
</config>

Each tag within this node is analogous to the tags from the top level <section /> node.

If you reload your page, you’ll now see an empty pop-open box with the title “Demo Of Config Fields”.

Adding Config Fields

Finally, we need to add our individual configuration fields. You’ll do this by adding a <fields /> node to your <messages /> node. We’ll start with a field name “hello_message”.

<!-- ... -->
<messages translate="label">
    <label>Demo Of Config Fields</label>
    <frontend_type>text</frontend_type>
    <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>                
    <fields>
        <hello_message>
            <label>Message</label>
            <frontend_type>text</frontend_type>
            <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>                    
        </hello_message>
    </fields>                   
</messages>
<!-- ... -->    

Again, fields within your new <hello_message> node are analogous to the other nodes you’ve added so far. However, this time <frontend_type>text</frontend_type> actually does something useful by letting the system know what kind of form element you want. Reload your page, and you should now see an individual text field in the popup box.

Once all this configuration information is in place, you’re done. No code is needed to save/load/update your config value. The system handles that all for you.

You’re not limited to text fields. Let’s add a field called <hello_time/>

<!-- ...-->
<fields>
    <hello_message>
        <label>Message</label>
        <frontend_type>text</frontend_type>
        <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>                    
    </hello_message>
    <hello_time>
        <label>Time to Say Hello</label>
        <frontend_type>time</frontend_type>
        <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>                    
    </hello_time>       
</fields>
<!-- ... -->

Notice that the main difference here is the

<frontend_type>time</frontend_type>

tag. Reload your page, and you’ve now got a config field for saving a time value.

Many, but not all, of the built in Varien data form classes (lib/Varien/Data/Form/Element) are supported. The <frontend_type /> tag acts as an identifier for a factory-ish pattern. Let’s try changing our hello message to a select field.

<!-- ... -->
<hello_message>
    <label>Message</label>
    <frontend_type>select</frontend_type>
    <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>                    
</hello_message>
<!-- ... -->

If you reload your page you’ll see that you have an HTML select, but without any values. We’re going to need to add a source model to our field definition. Try this instead.

<hello_message>
    <label>Message</label>
    <frontend_type>select</frontend_type>
    <!-- adding a source model -->
    <source_model>helloworld/words</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>                    
</hello_message>    

The <source_model> element defines a URI for a Model class that we’ll use to provide default values for the select. This means we’ll need to make sure that our module config.xml has its models section setup

File: app/code/local/Alanstormdotcom/Helloworld/etc/config.xml
<config>    
    <!-- ... -->
    <global>
    <!-- ... -->
        <models>
            <!-- ... -->
            <helloworld>
                <class>Alanstormdotcom_Helloworld_Model</class>
            </helloworld>   
            <!-- ... -->
        </models>
    </global>
</config>

See the Magento Models and ORM Basics and the Class Instantiation Abstraction and Autoload article if you’re not sure what’s going on in that config.

So, with that in place, if we reload our page after a cache clearing, we’ll end up with an Error something like

Warning: include(Alanstormdotcom/Helloworld/Model/Words.php)

That’s because we haven’t defined our source Model class. Let’s do that now.

Note: If the warning you’re getting uses a Mage/Helloworld/... path, that means you haven’t setup your <model /> section correctly in config.xml.

To define our source Model, add the following file

File: app/code/local/Alanstormdotcom/Helloworld/Model/Words.php
class Alanstormdotcom_Helloworld_Model_Words
{
    public function toOptionArray()
    {
        return array(
            array('value'=>1, 'label'=>Mage::helper('helloworld')->__('Hello')),
            array('value'=>2, 'label'=>Mage::helper('helloworld')->__('Goodbye')),
            array('value'=>3, 'label'=>Mage::helper('helloworld')->__('Yes')),            
            array('value'=>4, 'label'=>Mage::helper('helloworld')->__('No')),                       
        );
    }

}

Source Models are classes that respond to a method named toOptionsArray. This method should return an array of values that are used to populate the default values of our form elements (which descend from the Varien_Data_Form_Element_Abstract hierarchy). For a select element, this means defining a set of value/label pairs. In the above example, we’re passing our labels through the Helper’s translation method (__). While not necessary, this is always a good practice. You never know when you’re going to get big in Japan!

Reload your page, and you should have a working select field.

For those interested in the Magento internals, the initFields method in the following class is where the source Model is used to set the field’s value

app/code/core/Mage/Adminhtml/Block/System/Config/Form.php

Adding to Existing Config Sections/Groups

In addition to setting up your own config Tabs and sections, you can add to an existing System Config section by adding appropriate sections to your own system.xml file.

For example, if you add the following

File: app/code/local/Alanstormdotcom/Helloworld/etc/system.xml
<config>
    <!-- ... -->
    <sections>
        <!-- ... -->
        <general>
            <groups>
                <example>
                    <label>Example of Adding a Group</label>
                    <frontend_type>text</frontend_type>
                    <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>                    
                </example>
            </groups>
        </general>
        <!-- ... -->
    </section>
</config>

you’ll have a new group in the general Tab called “Example of Adding a Group”.

Retrieving Values

We’ve covered how to setup forms for creating configuration values. To retrieve values in our client applications and modules, we’ll use the getStoreConfig method on the global Mage object. For example, to grab the value of the select we created above, we’d use

Mage::getStoreConfig('helloworld_options/messages/hello_message');

The getStoreConfig method accepts a single parameter that’s a URI in the following format

section_name/group_name/field_name

You can also grab an array of all your config values by specifying a partial path

Mage::getStoreConfig('helloworld_options/messages');
Mage::getStoreConfig('helloworld_options');

Finally, if you need to grab a value for a store that isn’t the store being used by the current session, getStoreConfig accepts a second parameter, the store ID

Mage::getStoreConfig('helloworld_options',1);

Wrapup

We started off wanting to setup some System Config sections, and ended up exploring Helper classes, Access Control Lists, and the Varian Form hierarchy. In addition to what we covered above, it’s possible to create System Config options that use custom frontend and backend Models, which I’ll try to cover in a latter article.

You can download the complete module for this article here.

Like this article? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

类别 :  magento(258)  |  浏览(2704)  |  评论(0)
发表评论(评论将通过邮件发给作者):

Email: