Custom redirect logic in Magento

This tutorial will show you how to use controllers and actions in Magento to set up redirect logic to product pages based on custom URL patterns.

A lot of the time when we are building new websites to replace existing ones, we need to redirect certain URLs from the old website to new URLs on the new website. I also sometimes get asked by new clients what URLs they should print in marketing to direct customers to pages on their website. This can happen before the final URL structure has been decided on! My approach is to decide on a pattern, or identify an existing pattern, give this to the client, and let Magento do the hard work by working out where it needs to send the user. Then if the URL of the actual page changes at a later date, it shouldn’t matter, because the URL we are directing users to will redirect to the correct page.

Why can’t we just direct people to the actual URL?

Because Magento does not particularly care about keeping a product page URL permanent. It appends suffixes if two pages have the same URL key; it can change the URL on indexing; your admin user could change the URL key of a product and forget to tick “Create Permanent Redirect”; the product can become not visible individually if it becomes part of a Grouped or Bundle Product. I’m going to go into redirecting between product types in another tutorial.
There are a whole host of reasons not to trust that Magento’s product URLs will always stay the same.

I work with publishers so let’s use the example of a book product page. Maybe the publisher’s old website had product pages for a given book at website.com/book/9876543210123 (the ISBN). Now, provided we can relate that ISBN to something we hold against the product record (which we do; it’s the SKU we use for the product), we should be able to redirect that URL to the new product page, even if we ourselves don’t know exactly what URL that page exists at—the point is that Magento does. Doing it is actually pretty easy.

To begin, we’ll set up the folder and file structure we’ll need for our module. If you don’t know the basics of building a Magento module, you should learn this before continuing with this tutorial. It’s a pretty simple module, but I’m going to be as detailed as I can in my explanation.

app/
  code/
    local/
      See/
        Redirect/
          controllers/
            IndexController.php
          etc/
            config.xml
  etc/
    modules/
      See_Redirect.xml

See is what I am using for my “company name” (it’s my initials) and I’ve named the module simply “Redirect”.

See_Redirect.xml

This file just registers our small module with Magento. You should be familiar with it if you’ve built a module before.

<?xml version="1.0"?>
<config>
	<modules>
		<See_Redirect>
			<active>true</active>
			<codePool>local</codePool>
		</See_Redirect>
	</modules>
</config>

config.xml

	<?xml version="1.0"?>
	<config>
	  <modules>
	    <See_Redirect>
	      <version>0.1.0</version>
	    </See_Redirect>
	  </modules>
	  <frontend>
		<routers>
	        <see_redirect><!-- this tag could be anything -->
	            <use>standard</use>
	            <args>
	                <module>See_Redirect</module>
	                <frontName>redirect</frontName>
	            </args>
	        </see_redirect>
	    </routers>
	  </frontend>
	</config>

The highlighted text is where we define the frontName for our module. That is, the text that is used in URLs related to that module in the frontend (explained more below). The rest of the file is just standard for any module and defines the current version of the module.

Understanding frontnames, controllers and actions

You may be familiar with standard Magento pages like website.com/customer/account/create/ (the registration page) and website.com/checkout/cart/[index] (the basket page). If we take the first example, Magento is looking at the customer frontName, the AccountController, and the createAction function. It will render the page based on what is in those places.

So, in our module, we have already defined the frontName we are going to use; redirect. Now we need a controller. The go-to is IndexController.php, which is what we created in our initial structure. What about an action? Let’s use bookAction, which will be a function in the IndexController.php file. This means the code we are going to write will be triggered by going to the relative path redirect/index/book. Remember, it’s frontName/controller/action. Anything declared after that relative path is viewed as the parameters of the URL. For example, redirect/index/book/id/9876543210123/group/2 would result in an “id” parameter of “9876543210123” and a “group” parameter of “2”. We’ll revisit this in a second, and I’ll also explain how we rewrite this URL to match the pattern /book/[ISBN] we want to redirect.

IndexController.php

Let’s populate our IndexController file. I’m going to create the bookAction function and also a function loadNoRoute to use if we can’t find a redirect, which is taken from Magento’s default action for a 404 in app/code/core/Mage/Cms/controllers/IndexController.php (noRoute action).

<?php
class See_Redirect_IndexController extends Mage_Core_Controller_Front_Action
{
	public function bookAction(){

		$id = $this->getRequest()->getParam('id');
		
		// more to add here
        
	}
	
	protected function loadNoRoute() {
	    $this->getResponse()->setHeader('HTTP/1.1','404 Not Found');
	    $this->getResponse()->setHeader('Status','404 File not found');

	    $pageId = Mage::getStoreConfig(Mage_Cms_Helper_Page::XML_PATH_NO_ROUTE_PAGE);
	    if (!Mage::helper('cms/page')->renderPage($this, $pageId)) {
	        $this->_forward('defaultNoRoute');
	    }
	}
	
}

The highlighted line is how we get the “id” parameter I was talking about above.

Now, we need another function, findProduct. I’m not going to go into this method too much as this is not the point of this tutorial, but this is a standard way of finding a Magento product.

You can read more about using the product collection in this article.

	protected function findProduct($sku,$attributeSetId = null) {
		// Get the whole product collection
		$collection = Mage::getModel('catalog/product')->getCollection();
		// Only load the product_url attribute with the query
		// Much more efficient than loading all attributes
		$collection->addAttributeToSelect(array('product_url'));
		// Filtered the collection to match the product by SKU (the ISBN)
		// As SKU is a unique field, this should only match one product or zero
		$collection->addAttributeToFilter('sku',$sku);
		// Filter by attribute set ID, if you understand and use attribute sets.
		// It's an optional extra check, there are plenty more you could do.
		if ($attributeSetId) {
			$collection->addAttributeToFilter('attribute_set_id',$attributeSetId);
		}
		// Make sure the URL loaded is in its neatest form.
		$collection->addUrlRewrite();
		// Load the first result from the query.
		$product = $collection->getFirstItem();
		$product['data'];
				
		// Check we have loaded a product successfully
		if ($product->getId()) {
			// Get the URL and redirect to it.
			$url = $product->getProductUrl();
			if ($url) {
				$this->_redirectUrl($url);
				return true;
			}
			
		}
		// If we didn't find a matching product, load the default 404
		$this->loadNoRoute();
		return false;
	}

Now we just need to use this function in our book action and we are all set! See the full source of IndexController.php below.

<?php
class See_Redirect_IndexController extends Mage_Core_Controller_Front_Action
{
	public function bookAction(){

		$id = $this->getRequest()->getParam('id');
		
		$this->findProduct($id);
        
	}
	
	protected function loadNoRoute() {
	    $this->getResponse()->setHeader('HTTP/1.1','404 Not Found');
	    $this->getResponse()->setHeader('Status','404 File not found');

	    $pageId = Mage::getStoreConfig(Mage_Cms_Helper_Page::XML_PATH_NO_ROUTE_PAGE);
	    if (!Mage::helper('cms/page')->renderPage($this, $pageId)) {
	        $this->_forward('defaultNoRoute');
	    }
	}
	
	protected function findProduct($sku,$attributeSetId = null) {
		// Get the whole product collection
		$collection = Mage::getModel('catalog/product')->getCollection();
		// Only load the product_url attribute with the query
		// Much more efficient than loading all attributes
		$collection->addAttributeToSelect(array('product_url'));
		// Filtered the collection to match the product by SKU (the ISBN)
		// As SKU is a unique field, this should only match one product or zero
		$collection->addAttributeToFilter('sku',$sku);
		// Filter by attribute set ID, if you understand and use attribute sets.
		// It's an optional extra check, there are plenty more you could do.
		if ($attributeSetId) {
			$collection->addAttributeToFilter('attribute_set_id',$attributeSetId);
		}
		// Make sure the URL loaded is in its neatest form.
		$collection->addUrlRewrite();
		// Load the first result from the query.
		$product = $collection->getFirstItem();
		$product['data'];
				
		// Check we have loaded a product successfully
		if ($product->getId()) {
			// Get the URL and redirect to it.
			$url = $product->getProductUrl();
			if ($url) {
				$this->_redirectUrl($url);
				return true;
			}
			
		}
		// If we didn't find a matching product, load the default 404
		$this->loadNoRoute();
		return false;
	}
	
}

Rewriting what we’ve done to our redirect URL pattern

Now, if you go to website.com/redirect/index/book/id/9876543210123, you should be redirected to the product page of a product with SKU:9876543210123. If that product does not exist, you will legitimately go to a 404. But the whole point was that our URL pattern was going to be website.com/book/9876543210123, so how do we do that?

Easily. You just add a few lines to your config.xml.

<?xml version="1.0"?>
<config>
  <modules>
    <See_Redirect>
      <version>0.1.0</version>
    </See_Redirect>
  </modules>
  <frontend>
	<routers>
        <see_redirect><!-- this tag could be anything -->
            <use>standard</use>
            <args>
                <module>See_Redirect</module>
                <frontName>redirect</frontName>
            </args>
        </see_redirect>
    </routers>
  </frontend>
  <global>
      <rewrite>
          <see_redirect_book> <!-- unique identifier for our rewrite, again could be anything -->
              <from><![CDATA[#^/book/#]]></from>
              <to><![CDATA[/redirect/index/book/id/]]></to>
              <complete>1</complete>
          </see_redirect_book>
      </rewrite>
  </global>
</config>

This uses regular expressions and means any URL featuring /book/ will have that part rewritten to /redirect/index/book/id/. This is exactly the form we want. Now if we go to website.com/book/9876543210123, we should have exactly the same behaviour as if we go to website.com/redirect/index/book/id/9876543210123.

We’ve done what we wanted to!

Leave a Reply

Your email address will not be published. Required fields are marked *