Magento is one of the best E-Commerce platforms currently on the internet. However, it doesn’t seem to take into account one of the most important SEO rules which states that the URL key which is used to create the product’s URL should be SEO-friendly. We can still write the url key in english for our arabic products but this won’t exactly match this rule to have the arabic keywords of the product’s title in the product url as well.
I rolled up my sleeves and got to work. What i did was a 2 stage tracing / debugging session. I will also be writing this in a magento module to group this functionality all together into a reusable component.
Let’s start preparing for the module we’re going to create. All you have to do, assuming you name your company “XYZ” is the following:
1) Create a folder called “XYZ” in your app/code/local directory.
2) Inside XYZ, create a folder called “etc” and another folder called “Model”.
3) Inside XYZ/etc create a file called config.xml and add the following:
<?xml version="1.0" encoding="UTF-8"?> <config> <modules> <XYZ_UnicodeUrls> <version>0.0.1</version> </XYZ_UnicodeUrls> </modules> </config>
Now… here’s what i’ve done.
1) The first problem was that when entering the product key in arabic, the product got saved with an empty url key. Using Zend Studio and xdebug’s remote debugging, i found out that product entity attributes were being filtered. One of the filters passed on the url_key field was “Mage_Catalog_Model_Product_Url”. The method being called in that filter is “formatUrlKey” which takes the URL key as a string:
public function formatUrlKey($str) { $urlKey = preg_replace('#[^0-9a-z]+#i', '-', Mage::helper('catalog/product_url')->format($str)); $urlKey = strtolower($urlKey); $urlKey = trim($urlKey, '-'); return $urlKey; }
As you can see from the regex in the first line of the function’s body. All characters are being removed apart from 0 to 9 and a – z (small case). All we had to do here is override the function to become:
public function formatUrlKey($str) { $urlKey = Mage::helper('catalog/product_url')->format($str); $urlKey = strtolower($urlKey); $urlKey = trim($urlKey, '-'); return $urlKey; }
So the final model code should become:
<?php class XYZ_UnicodeUrls_Model_Product_Url extends Mage_Catalog_Model_Product_Url { public function formatUrlKey($str) { $urlKey = Mage::helper('catalog/product_url')->format($str); $urlKey = strtolower($urlKey); $urlKey = trim($urlKey, '-'); return $urlKey; } }
Make sure you add the following to your config.xml so that magento can rewrite the original model with your one.
<global> <models> .... <catalog> <rewrite> <product_url>N2Vlabs_UnicodeUrls_Model_Product_Url</product_url> </rewrite> </catalog> ..... </models> </global>
2) Even if you’ve solved the problem of adding a unicode url key. Now we have to make sure magento was finding a record for this URL inside table “core_url_rewrite” when looking for it.
After digging a bit deep into what is going on, i also found out that “Mage_Core_Model_Resource_Url_Rewrite” was looking up the database for this URI in a method:
public function loadByRequestPath(Mage_Core_Model_Url_Rewrite $object, $path)
When having an arabic URI, the URI is captured url encoded inside this method’s loop that looks as follows:
foreach ($path as $key => $url) { $pathBind['path' . $key] = $url; }
All we had to do here is to make sure we url decode $url to become:
foreach ($path as $key => $url) { $pathBind['path' . $key] = urldecode($url); }
The whole code looks as follows:
<?php class XYZ_UnicodeUrls_Model_Resource_Url_Rewrite extends Mage_Core_Model_Resource_Url_Rewrite { public function loadByRequestPath(Mage_Core_Model_Url_Rewrite $object, $path) { if (!is_array($path)) { $path = array($path); } $pathBind = array(); foreach ($path as $key => $url) { $pathBind['path' . $key] = urldecode($url); } // Form select $adapter = $this->_getReadAdapter(); $select = $adapter->select('*') ->from($this->getMainTable()) ->where('request_path IN (:' . implode(', :', array_flip($pathBind)) . ')') ->where('store_id IN(?)', array(Mage_Core_Model_App::ADMIN_STORE_ID, (int)$object->getStoreId())); $items = $adapter->fetchAll($select, $pathBind); // Go through all found records and choose one with lowest penalty - earlier path in array, concrete store $mapPenalty = array_flip(array_values($path)); // we got mapping array(path => index), lower index - better $currentPenalty = null; $foundItem = null; foreach ($items as $item) { $penalty = $mapPenalty[$item['request_path']] << 1 + ($item['store_id'] ? 0 : 1); if (!$foundItem || $currentPenalty > $penalty) { $foundItem = $item; $currentPenalty = $penalty; if (!$currentPenalty) { break; // Found best matching item with zero penalty, no reason to continue } } } // Set data and finish loading if ($foundItem) { $object->setData($foundItem); } // Finish $this->unserializeFields($object); $this->_afterLoad($object); return $this; } }
And you have to add the following to your config.xml to override the original model.
<global> <models> .... <core_resource> <rewrite> <url_rewrite>XYZ_UnicodeUrls_Model_Resource_Url_Rewrite</url_rewrite> </rewrite> </core_resource> .... </models> </global>
If you’d like to get the whole extension code from github… here’s the link
That’s all folks… see ya next time.

