Editing Magento’s Top Links (The Better Way)

You might need to read the post about using a local.xml file before this post makes much sense.

We offer all our clients a completely customized design from scratch, which means changing anything – including those annoying defaulted top links.  This post will show you how to edit your top links (without editing core layout files which may change when you update Magento) by utilizing a local.xml file. You can even add your own custom links without touching any template files! [NOTE: This specific example assumes you are using the blank theme. Layout handles may differ from theme to theme.]

In local.xml:

<?xml version="1.0"?>
<layout version="0.1.0">
    <default>
        <reference name="root">
            <reference name="top.links">
                <!-- Add custom links. Pretty self-explanatory.
                Dig into app/code/core/Mage/Page/Block/Template/Links.php for more info -->
                <action method="addLink" translate="label title">
                    <label>About Us</label>
                    <url>about</url>  <!-- can use full url also -->
                    <title>About Us</title>
                    <prepare>true</prepare> <!-- set true if adding base url param -->
                    <urlParams helper="core/url/getHomeUrl"/> <!-- base url - thanks @Russ! -->
                    <!-- there are a few param you can send to do different things in <urlParams> 
                             dig into app/code/core/Mage/Core/Model/Url.php, around line 803 -->                   

                    <!-- below adds #add-fragment to the end of your url -->
                    <!-- <urlParams><_fragment>add-fragment</_fragment></urlParams> -->

                    <!-- below adds ?add-query to the end of your url -->
                    <!-- <urlParams><_query>add-fragment</_query></urlParams> -->

                    <!-- below gives you a new session id (i think...)-->
                    <!-- <urlParams><_nosid>true</_nosid></urlParams> -->

                    <!-- below replaces double quotes, single quotes, greater than, and less than signs 
                             to their respective url escaped replacements (%22, %27, %3E, %3C) -->
                    <!-- <urlParams><_escape>i'm-a-blog-url</_escape></urlParams> -->

                    <position>1</position>
                    <liParams/>
                    <aParams>class="top-link-about-us"</aParams>
                    <beforeText></beforeText>
                    <afterText></afterText>
                </action>

                <!-- Removes 'My Account' link - Default position: 10 -->
                <action method="removeLinkByUrl"><url helper="customer/getAccountUrl"/></action>

                <!-- Removes 'Wishlist' link - Default position: 20 -->
                <!-- for Magento 1.3.x -->
                <action method="removeLinkByUrl"><url helper="wishlist/"/></action>

                <!-- for Magento 1.4.x -->
                <remove name="wishlist_link"/>

                <!-- Removes 'My Cart' AND 'Checkout' links
                Default position: 40 and 50 respectively -->
                <remove name="checkout_cart_link"/>

                <!-- To re-add 'My Cart' or 'Checkout' after removing both -->
                <block type="checkout/links" name="checkout_cart_link_custom">
                    <action method="addCartLink"></action>
                    <action method="addCheckoutLink"></action>
                </block>
            </reference>
        </reference>
    </default>

    <customer_logged_out>
        <!-- Removes 'Log In' link - Default position: 60 -->
        <reference name="top.links">
            <action method="removeLinkByUrl"><url helper="customer/getLoginUrl"/></action>
        </reference>
    </customer_logged_out>

    <customer_logged_in>
        <!-- Removes 'Log Out' link - Default position: 60 -->
        <reference name="top.links">
            <action method="removeLinkByUrl"><url helper="customer/getLogoutUrl"/></action>
        </reference>
    </customer_logged_in>

</layout>

If you absolutely cannot find a way to customize your top links using these methods, you can edit the /template/page/template/links.phtml

Optimizing Your E-Commerce Conversion Rate: The Two Conversion Criteria

E-commerce traffic is worthless if you are not converting any of your traffic into sales.  The percentage of visits that result in a purchase is called the conversion rate.  So much emphasis is put on generating traffic, but it’s only the first step in building e-commerce revenue and profitability.  It is typically very expensive from a holistic resource perspective (money, time, focus, etc.) to generate traffic, so to pay no attention to conversion rate is committing a cardinal e-commerce sin.  There are a few critical components that determine how high your conversion rate will be.

Step back for a moment.  What would it take to produce a 100% conversion rate?  Every single time someone visited your site, they would purchase something.  Anytime a visitor meets two criteria, they make a purchase.  There are many factors that affect each of these criteria, and I will analyze each of them in this article and following articles.

THE TWO CONVERSION CRITERIA

(Here it is.  The secret recipe for converting visits to sales.)

To convert a visit into a purchase, the visitor must:

1.  Perceive the value of what you offer is higher than the value of the cost to acquire it.

2.  Have the resources to absorb the cost at the point of purchase.

I will analyze these Conversion Criteria from the perspective of a different set of variables.  While it isn’t a quantitative measurement, you can determine the likelihood that an individual will make a purchase via a cubic equation:  Traffic Quality x Value Presentation x Ease of Purchase.  If you have a very high value for any of these variables, it doesn’t necessarily mean you will have a high conversion rate.  Just as with the broader e-commerce revenue equation I covered in the first post of this series (Traffic x Conversion Rate x Average Order Size = Revenue), attention must be given to each of the variables to optimize the outcome.  If any one of them is shirked, it can ruin all of the efforts made to improve the other two.  While you cannot attach a numeric value to these variables as easily as you can to the Revenue variables, it’s still important to understand conceptually because the conversion variables relate in the same way as the revenue variables.

In the next article, I will dig into the first variable that affects conversion rate:  Traffic Quality.

The Better Way to Modify Magento Layouts

In this article, I’m going to be covering what I believe to be a very effective way of modifying the layout of any Magento theme.

For several of the first Magento themes I built, I copied the layout files from the default or blank theme into the custom theme layout folder. I would then modify the layout files directly, editing or commenting out content in files like: catalog.xml, page.xml, checkout.xml, etc… I never liked editing these files directly, as I knew that when it came time to upgrade to a newer version of Magento that had upgraded the layout files, I’d have to merge the changes into the new layout files.

One day, I was digging through the Magento code relating to layout files and discovered a bit of code that made me realize that it was possible to just place a local.xml file in my custom theme’s layout folder and have it loaded automatically by Magento. (this code is on line 283 in /app/code/core/Mage/Core/Model/Layout/Update.php in the fetchFileLayoutUpdates() method).

Due to Magento’s brilliant tags, it’s possible to do just about anything you want without having to edit any of the default layout files.

Before delving into the code, let’s look at the advantages/disadvantages of this method:

Advantages

  • Allows you to upgrade themes without having to merge in changes
  • All custom layout changes are centralized, allowing developers to more easily make changes to custom theme elements

Disadvantages

  • At first, it’s slower to use this method than hacking up the standard layout files directly
  • You will have one more place to look where the site might be pulling code (template phtmls, standard layout files, admin layout updates, AND local.xml)

Here is the slimmed down, commented local.xml from one of our recent projects:

<?xml version="1.0"?>
<layout version="0.1.0">

<default>

	<reference name="head">
		<!-- Magento looks in /skin/frontend/<INTERFACE>/<THEME>/js/buyprinting.js
		for this file -->
		<action method="addItem"><type>skin_js</type><name>js/buyprinting.js</name></action>

		<!-- This removes the item that was set in the page.xml file -->
		<action method="removeItem"><type>skin_js</type><name>js/iehover-fix.js</name></action>

		<!-- Magento looks in /js/prototype/element.storage.js for this file -->
		<action method="addJs"><name>prototype/element.storage.js</name></action>

		<action method="addCss"><stylesheet>css/buyprinting.css</stylesheet></action>
	</reference>

	<reference name="header">
        <!-- This adds a CMS block that can be called from the template file associated with the header block. -->
		<block type="cms/block" name="cms_quick_help">
			<action method="setBlockId"><block_id>quick_help</block_id></action>
		</block>

        <!-- The remove tag removes the blocks with the specified name from the layout -->
		<remove name="top.menu"/>
		<remove name="store_language"/>
		<remove name="breadcrumbs"/>
	</reference>

	<reference name="top.nav">
		<remove name="catalog.topnav"/>
	</reference>

	<reference name="left">
		<remove name="left.newsletter"/>
		<remove name="left.permanent.callout"/>
		<remove name="catalogsearch.leftnav"/>

        <!-- When you use the remove tag, it removes any blocks with the specified name from
            the entire layout, regardless of the context. So, if I remove right.newsletter in
            the <default> context and that name is used in say the <catalog_product_view> context,
            then both blocks will be removed.  Because remove operates on the global context,
            you can only remove an element once.  Since <remove name="right.newsletter" /> is
            being called in catalogsearch.xml, we have to unset it, or else we'll get an error.

            The line below only unsets the block from the parent's context, not the global
            layout context -->
		<action method="unsetChild"><name>right.newsletter</name></action>
	</reference>

	<reference name="right">
        <!-- Some blocks have to be removed using remove, others via unsetChild.
            I've not spent the time digging into the code to figure out why -->
		<remove name="right.permanent.callout"/>
		<remove name="catalog.compare.sidebar"/>
		<remove name="left.reports.product.viewed"/>
		<action method="unsetChild"><name>sale.reorder.sidebar</name></action>
		<action method="unsetChild"><name>wishlist_sidebar</name></action>
		<action method="unsetChild"><name>right.reports.product.viewed</name></action>
		<remove name="cart_sidebar"/>
	</reference>

</default>

<!-- CATALOG PAGES -->
	<catalog_product_view><!-- 2columns-right -->
		<reference name="root">
			<action method="setTemplate"><template>page/2columns-left.phtml</template></action>
		</reference>
		<reference name="content">
			<reference name="product.info">
				<block type="cms/block" name="cms_product_info_tabs">
					<action method="setBlockId"><block_id>product_info_tabs</block_id></action>
				</block>
				<block type="catalog/product_view" name="product.clone_prices" as="prices" template="catalog/product/view/price_clone.phtml"/>
				<action method="unsetChild"><name>tierprices</name></action>
				<action method="unsetChild"><name>addto</name></action>
				<remove name="addto"/>
				<reference name="product.info.options.wrapper.bottom">
					<action method="unsetChild"><name>product.tierprices</name></action>
				</reference>
			</reference>
		</reference>
	</catalog_product_view>

</layout>

I hope with article has given you some direction as to how you can improve you Magento theming skills. If you have any additional tips/comments on coding layouts, please suggest them in the comments section.

The Smart Method of Loading Collections

One fairly unknown feature of Magento collections is that you actually don’t have to call ->load() on a collection before being able to access the items in the collection. So you can do this:

$orders = Mage::getResourceModel('sales/order_collection')->addAttributeToSelect('*');
# The load method is not necessary, as iterating through a collection automatically loads it, if it hasn't already been loaded.
# $orders->load();
foreach($orders as $order){
    echo 'Order Id: ' . $order->getId() . "n";
}

The base Varien_Data_Collection class implements the IteratorAggregate (http://php.net/manual/en/class.iteratoraggregate.php) interface which extends the Traversable (http://www.php.net/manual/en/class.traversable.php) interface. When a class extends the Traversable interface, it guarantees that that class can be iterated through using foreach(). When foreach is called on a collection, it calls the getIterator() method in the Varien_Data_Collection class and uses the value returned from that method as the value that the foreach iterates through. This is the getIterator() method:

    public function getIterator()
    {
        $this->load();
        return new ArrayIterator($this->_items);
    }

This auto-loading functionality works for both EAV and flat-table collections.

As you can see, a collection ensures that it is loaded before running through a for each loop. Note: collections can only be loaded once per instantiation. If you want to reload a collection, you have to call the clear() method, and then reset the select and filters before calling the load() method again.

Knowing that collections function in this way allows you to write code without explicit calls to load(). This ultimately should result in more flexible code. You can have a method in a block that loads a collection. That collection can then either be called by a template file and iterated through, or you can have another method that loads the collection from the first method, and then adds additional selects/filters to it. This practice of not explicitly calling the load() results in more flexible and reusable code.

Saving the Value of a Specific Attribute from a Model

In Magento, it’s very easy to save all the data in a model by running $model->save();. (Note: In this blog post, model refers to an EAV model, not a flat resource model) This saves all the attributes for the model to their respective attribute tables. There are times when saving the value of just one of the model attributes is desirable.

A couple cases where you’d want to do this:

  • You’ve been passed a model from an event, and you aren’t sure if the data in that model can be safely saved.
  • You are saving many models and want to make your save operations as efficient as possible

Here is how you’d save just one attribute of a product model:

$product = Mage::getModel('catalog/product')->load(1);
$product->setName('Some Random Name');
$product->getResource()->saveAttribute($product, 'name');

PHP Single vs. Double Quotes

For a long time, I’ve been a proponent of using single-quotes as opposed to double-quotes when assigning string literals (strings containing no variables) to a variable (eg: $var = ‘string’; VS $var = “string”). I just did some research to find out how much (if any) impact using single-vs-double quotes had on performance.

The results were fairly consistent: Time 1: 5.9485120773315 ($c = “test ” . $i; ) Time 2: 7.0326972007751 ($c = “test $i”; ) Time 3: 5.9164550304413 ($c = ‘test ‘ . $i; ) It’s clear that embedding variables in strings is less efficient, but the difference between normal single-vs-double quotes is negligible (at least on our server configuration with this test). Despite the lack of clear performance benefits, I still think it is best practice to use single-quotes when using string literals, as it denotes that a string doesn’t contain any variables. One benefit of this is that when skimming code, you can more quickly process which variables are string literals vs strings with embedded variables. The Zend Framework Coding Standard also recommends this practice:http://framework.zend.com/manual/en/coding-standard.coding-style.html

Contact Us