Overall structure of an address and its elements is well represented in the first comment to the app/code/core/Mage/Core/Model/Url.php model. You can refresh your knowledge there.
Apache mod_rewrite module plays an important role in URL rewriting. Rules, specified in .htaccess, will not be processed without it. In order to activate URL rewriting, .htaccess.sample file should be renamed .htaccess. The following part is responsible for rewriting:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<IfModule mod_rewrite.c> ############################################ ## enable rewrites Options +FollowSymLinks RewriteEngine on ############################################ ## you can put here your magento root folder ## path relative to web root #RewriteBase /magento/ ############################################ ## workaround for HTTP authorization ## in CGI environment RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] ############################################ ## always send 404 on missing files in these folders RewriteCond %{REQUEST_URI} !^/(media|skin|js)/ ############################################ ## never rewrite for existing files, directories and links RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-l ############################################ ## rewrite everything else to index.php RewriteRule .* index.php [L] </IfModule> |
Moreover, the following parameter should be activated in the Admin Panel:
Web -> Search Engines Optimization -> Use Web Server Rewrite
Basic script will thereby be excluded from an address line. URL rewriting will work after these changes.
In Front controller article dispatch() method was described; let’s take a look at the following part of this method:
1 2 3 4 5 6 7 8 |
if (!$request->isStraight()) { Varien_Profiler::start('mage::dispatch::db_url_rewrite'); Mage::getModel('core/url_rewrite')->rewrite(); Varien_Profiler::stop('mage::dispatch::db_url_rewrite'); } Varien_Profiler::start('mage::dispatch::config_url_rewrite'); $this->rewrite(); Varien_Profiler::stop('mage::dispatch::config_url_rewrite'); |
URL rewriting takes place here. There are two URL rewriting types in Magento:
- URL rewriting with database
- URL rewriting via configuration files
Let’s take a look at the first type of URL rewriting. It allows rewriting with the help of Mage_Core_Model_Url model, according to the rules stored in the database. They can be created in the admin interface: Catalog->URL Rewrite Management. Two variants are present there:
- System
- Category
- Product
- Custom
System – URLs are created by system on the assumption of the data entered in the particular case. Administrators can also create their own addresses.
Custom – URLs are created by administrators. It can also be specified here if the link redirects to a different site (301 or 302). More will be said later on.
Let’s look more closely at the structure of the table where URL rewriting data is stored.
Table content tells us about fields purposes for each type.
System (Category)
Custom (Product)
Custom (Product)
Custom (Category)
Options field is considered below in details.
Let’s return to dispatch() method. First, it’s figured out if address rewriting is necessary:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if (!$request->isStraight()) { Varien_Profiler::start('mage::dispatch::db_url_rewrite'); Mage::getModel('core/url_rewrite')->rewrite(); Varien_Profiler::stop('mage::dispatch::db_url_rewrite'); } app/code/core/Mage/Core/Controller/Request/Http.php: public function isStraight($flag = null) { if ($flag !== null) { $this->_isStraight = $flag; } return $this->_isStraight; } |
Next, Mage_Core_Model_Resource_Url_Rewrite object is created and its rewrites() method is called. The method searches and rewrites addresses. First, validation is performed:
1 2 3 4 5 6 7 8 9 10 11 12 |
if (!Mage::isInstalled()) { return false; } if (is_null($request)) { $request = Mage::app()->getFrontController()->getRequest(); } if (is_null($response)) { $response = Mage::app()->getFrontController()->getResponse(); } if (is_null($this->getStoreId()) || false===$this->getStoreId()) { $this->setStoreId(Mage::app()->getStore()->getId()); } |
Next, different variants of a current path, used for database search for this request approval, are prepared.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$requestCases = array(); $pathInfo = $request->getPathInfo(); $origSlash = (substr($pathInfo, -1) == '/') ? '/' : ''; $requestPath = trim($pathInfo, '/'); // If there were final slash - add nothing to less priority paths. And vice versa. $altSlash = $origSlash ? '' : '/'; $queryString = $this->_getQueryString(); // Query params in request, matching "path + query" has more priority if ($queryString) { $requestCases[] = $requestPath . $origSlash . '?' . $queryString; $requestCases[] = $requestPath . $altSlash . '?' . $queryString; } $requestCases[] = $requestPath . $origSlash; $requestCases[] = $requestPath . $altSlash; |
Addresses can have different structure but the same sense. That is what taken into account here.
- furniture/
- furniture
Attention should also be drawn to the address of the following kind:
1 |
furniture?param=value |
They have a high priority and are processed in the first place, because they get into the array first. Next, Mage_Core_Model_Resource_Url_Rewrite resource with its loadByRequestPath: method comes in.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
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] = $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) { if (!array_key_exists($item['request_path'], $mapPenalty)) { continue; } $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; } |
Here is where the following request is prepared and executed:
1 2 |
SELECT `core_url_rewrite`.* FROM `core_url_rewrite` WHERE (request_path IN (‘furniture.html’, ‘furniture.html/’)) AND (store_id IN(0, 1)) |
Search depends on the admin store and “current” store. All the found lines are selected from the database and requested path correspondence search is triggered.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$mapPenalty = array_flip(array_values($path)); // we got mapping array(path => index), lower index - better $currentPenalty = null; $foundItem = null; foreach ($items as $item) { if (!array_key_exists($item['request_path'], $mapPenalty)) { continue; } $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 } } } |
Let’s next return to Mage_Core_Model_Url_Rewrite:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/** * Try to find rewrite by request path at first, if no luck - try to find by id_path */ if (!$this->getId() && isset($_GET['___from_store'])) { try { $fromStoreId = Mage::app()->getStore($_GET['___from_store'])->getId(); } catch (Exception $e) { return false; } $this->setStoreId($fromStoreId)->loadByRequestPath($requestCases); if (!$this->getId()) { return false; } $currentStore = Mage::app()->getStore(); $this->setStoreId($currentStore->getId())->loadByIdPath($this->getIdPath()); Mage::app()->getCookie()->set(Mage_Core_Model_Store::COOKIE_NAME, $currentStore->getCode(), true); $targetUrl = $request->getBaseUrl(). '/' . $this->getRequestPath(); $this->_sendRedirectHeaders($targetUrl, true); } |
If no matches are found (i.e. database search did not have any results and there is a $_GET[‘___from_store’] – code of another store), we look for matches taking the store into account.
1 |
$request->setAlias(self::REWRITE_REQUEST_PATH_ALIAS, $this->getRequestPath()); |
There are two redirect types in Magento
301 redirect – RP
302 redirect – P
302 redirect is being used by default.
1 2 3 4 5 6 7 8 9 10 11 |
protected function _sendRedirectHeaders($url, $isPermanent = false) { if ($isPermanent) { header('HTTP/1.1 301 Moved Permanently'); } header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0'); header('Pragma: no-cache'); header('Location: ' . $url); exit; } |
$external = substr($this->getTargetPath(), 0, 6); – selecting the protocol, if there is one.
Checking what redirect type exactly we will execute:
1 |
$isPermanentRedirectOption = $this->hasOption('RP'); |
Checking if the target address is external, if yes – the redirect is executed:
1 2 3 4 5 6 7 8 |
if ($external === 'http:/' || $external === 'https:') { $destinationStoreCode = Mage::app()->getStore($this->getStoreId())->getCode(); Mage::app()->getCookie()->set(Mage_Core_Model_Store::COOKIE_NAME, $destinationStoreCode, true); $this->_sendRedirectHeaders($this->getTargetPath(), $isPermanentRedirectOption); } else { $targetUrl = $request->getBaseUrl(). '/' . $this->getTargetPath(); } |
If it’s not external and one of redirect types is detected, we’ll do the redirection to the target address, taking into account the specified redirect type.
1 2 3 4 5 6 7 |
if ($isRedirectOption || $isPermanentRedirectOption) { if (Mage::getStoreConfig('web/url/use_store') && $storeCode = Mage::app()->getStore()->getCode()) { $targetUrl = $request->getBaseUrl(). '/' . $storeCode . '/' .$this->getTargetPath(); } $this->_sendRedirectHeaders($targetUrl, $isPermanentRedirectOption); } |
This is a Custom rules types processing, created by system or administrator. This mechanism can be applied to the scenarios of changing the category or product URL Key while moving the product from one category to another. In this situation, when you save, RP redirection to the old address is created. Custom record with the generated path_id will be created.
app/code/core/Mage/Catalog/Model/Url.php
1 2 3 4 5 6 7 8 9 10 11 12 13 |
protected function _saveRewriteHistory($rewriteData, $rewrite) { if ($rewrite instanceof Varien_Object && $rewrite->getId()) { $rewriteData['target_path'] = $rewriteData['request_path']; $rewriteData['request_path'] = $rewrite->getRequestPath(); $rewriteData['id_path'] = $this->generateUniqueIdPath(); $rewriteData['is_system'] = 0; $rewriteData['options'] = 'RP'; // Redirect = Permanent $this->getResource()->saveRewriteHistory($rewriteData); } return $this; } |
Of the following kind:
For this rewriting type, RP is selected for options by default.
This mechanism is used in the indexation. We’ll return to it later.
If we specify an “unusual” store by default, target address will be created considering the store:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
if (Mage::getStoreConfig('web/url/use_store') && $storeCode = Mage::app()->getStore()->getCode()) { $targetUrl = $request->getBaseUrl(). '/' . $storeCode . '/' .$this->getTargetPath(); } Parameters are added to the target path, if there are any: $queryString = $this->_getQueryString(); if ($queryString) { $targetUrl .= '?'.$queryString; } REQUEST_URI and PATH_INFO are specified for the current request: $request->setRequestUri($targetUrl); lib/Zend/Controller/Request/Http.php: public function setRequestUri($requestUri = null) { if ($requestUri === null) { …... } elseif (!is_string($requestUri)) { ….... } else { // Set GET items, if available if (false !== ($pos = strpos($requestUri, '?'))) { // Get key => value pairs and set $_GET $query = substr($requestUri, $pos + 1); parse_str($query, $vars); $this->setQuery($vars); } } $this->_requestUri = $requestUri; return $this; } $request->setPathInfo($this->getTargetPath()); app/code/core/Mage/Core/Controller/Request/Http.php public function setPathInfo($pathInfo = null) { if ($pathInfo === null) { … } $this->_pathInfo = (string) $pathInfo; return $this; } |
That ends the URL rewriting.
Before we focus on the second rewriting type, let’s take a look at indexation process for the addresses.
This functionality can be found here: System->Index Management->Catalog URL Rewrites.
Indexation module is defined in the index config.xml section of the catalog module:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<index> <indexer> <catalog_product_attribute> <model>catalog/product_indexer_eav</model> </catalog_product_attribute> <catalog_product_price> <model>catalog/product_indexer_price</model> </catalog_product_price> <catalog_url> <model>catalog/indexer_url</model> </catalog_url> <catalog_product_flat> <model>catalog/product_indexer_flat</model> </catalog_product_flat> <catalog_category_flat> <model>catalog/category_indexer_flat</model> </catalog_category_flat> <catalog_category_product> <model>catalog/category_indexer_product</model> </catalog_category_product> </indexer> </index> |
Addresses start to be processed in the following module:
app/code/core/Mage/Catalog/Model/Indexer/Url.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/** * Rebuild all index data */ public function reindexAll() { /** @var $resourceModel Mage_Catalog_Model_Resource_Url */ $resourceModel = Mage::getResourceSingleton('catalog/url'); $resourceModel->beginTransaction(); try { Mage::getSingleton('catalog/url')->refreshRewrites(); $resourceModel->commit(); } catch (Exception $e) { $resourceModel->rollBack(); throw $e; } } |
Generation mechanism is located here:
app/code/core/Mage/Catalog/Model/Url.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public function refreshRewrites($storeId = null) { if (is_null($storeId)) { foreach ($this->getStores() as $store) { $this->refreshRewrites($store->getId()); } return $this; } $this->clearStoreInvalidRewrites($storeId); $this->refreshCategoryRewrite($this->getStores($storeId)->getRootCategoryId(), $storeId, false); $this->refreshProductRewrites($storeId); $this->getResource()->clearCategoryProduct($storeId); return $this; } |
Let’s take a look at the main stages of the work of this mechanism:
1. If store is not specified, the following is executed for all the existing ones:
1 2 3 4 5 6 |
if (is_null($storeId)) { foreach ($this->getStores() as $store) { $this->refreshRewrites($store->getId()); } return $this; } |
2. All the old rewriting rules for category and products are found and deleted:
1 |
$this->clearStoreInvalidRewrites($storeId); |
3. Rewriting rules for categories and products are updated:
1 2 |
$this->refreshCategoryRewrite($this->getStores($storeId)->getRootCategoryId(), $storeId, false); $this->refreshProductRewrites($storeId); |
4. Unused rewriting rules for products (when the product is moved to another category) are found and deleted:
1 |
$this->getResource()->clearCategoryProduct($storeId); |
These are the main stages of addresses rewriting with the help of Magento database rules. The next part of the article will be devoted to URL rewrite with rules specified in configuration files.
Dinoop,
1. You can redirect from one URL to another one by using URL Rewrite Management in Magento or in .htaccess file.
2. You need to create route processing for that specific module and there you specify the address of the required template.
I installed a magento extension Medma Customer Avatar.
When I click one of my avatar the link goes to
http://localhost/literino/avatar/listavatar/getuserdetailandproduct/cid/29
How can I shorten it to
http://localhost/literino/author/29
Mind Blowing..!!