1

I am trying to forward every category page to a product view page using an event observer. But it seems to be not working.

Scenario : I am playing around with Magento router now. As part of this, I am trying to forward every category page in my Magento instance to the product page. The product page which is going to load for a category is the page of first product in that category.

What I have Tried : I observed to the event controller_action_predispatch_catalog_category_view and added below code to my observer method.

public function forwardSingleProCategory(Varien_Event_Observer $observer)
{
    //get event data
    $action = $observer->getControllerAction();
    $request = $action->getRequest();

    //get category id
    $categoryId = (int)$request->getParam('id', false);

    //grab category collection
    $layer = Mage::getModel('catalog/layer')->setCurrentCategory($categoryId);
    $collection = $layer->getProductCollection();

    //check whether category count is 1. If YES, then do magic
    if ($collection->getSize() > 0) {

        //prepare parameters that needs for the action `catalog/product/view`
        $product = $collection->getFirstItem();
        $actionName = 'view';
        $controllerName = 'product';
        $moduleName = 'catalog';
        $params = array(
            'category' => $categoryId,
            'id'       => $product->getId()
        );

        //tells no to category page router processing further
        $request->setDispatched(false);

        //throw exception with a clear message we need a product page.
        $e = new Mage_Core_Controller_Varien_Exception();
        $e->prepareForward($actionName, $controllerName, $moduleName, $params);
        throw $e;

    }

    return $this;
}

See the comments that I have put in the code. If something is not clear in my code please let me know. I can explain

But I am getting 404 Page for every category page request. Ooops !!!

How much I debug : The 404 page is triggering from Mage_Catalog_ProductController itself (which means observer is working fine). The problem lies here in this section :

 public function viewAction()
{
    // Get initial data from request
    $categoryId = (int) $this->getRequest()->getParam('category', false);
    $productId  = (int) $this->getRequest()->getParam('id');
    $specifyOptions = $this->getRequest()->getParam('options');
    ...
    try {
        $viewHelper->prepareAndRender($productId, $this, $params);
    } catch (Exception $e) {
        if ($e->getCode() == $viewHelper->ERR_NO_PRODUCT_LOADED) {
            if (isset($_GET['store'])  && !$this->getResponse()->isRedirect()) {
                $this->_redirect('');
            } elseif (!$this->getResponse()->isRedirect()) {
                $this->_forward('noRoute');
            }
        } else {
            Mage::logException($e);
            $this->_forward('noRoute');
        }
    }

The problem is $productId and $categoryId are same (seems that both holds category id value) and hence the line $viewHelper->prepareAndRender($productId, $this, $params); is failing and thus I am getting 404 page.

But in my observer, you can see that I am setting both category and id parameters via this section

$e = new Mage_Core_Controller_Varien_Exception();
$e->prepareForward($actionName, $controllerName, $moduleName, $params);

I double checked and confirmed that $params (holds the right product id for id and right category id for category) are correct in my observer.

So my question is how it is get rewriting by category Id in the router process in this scenario ?

How can I make it work ?

1

instead of

$e->prepareForward($actionName, $controllerName, $moduleName, $params);

try

$request->setModuleName($moduleName)
        ->setControllerName($controllerName)
        ->setActionName($actionName)
        ->setParam('id', $product->getId())
        ->setParam('category_id', $id);
  • this is what I tried first.. But it gives me lot of errors. dispatch() method in Mage_Core_Controller_Varien_Action appears to trigger _forward action only when we have an exception of type Mage_Core_Controller_Varien_Exception. (I know the above code also do the same thing). Still the method above is not going to make any change if we didnt add setDisptched(false) to $request. Any way I am going to try this once more – Rajeev K Tomy Sep 10 '15 at 6:12
  • I got 404 for the above code. Double checked it. – Rajeev K Tomy Sep 10 '15 at 6:15
  • and I got that exception idea from @Fabian : – Rajeev K Tomy Sep 10 '15 at 6:20
  • I found the problem and put it as a solution. If you have time, please have a look at it :-) – Rajeev K Tomy Sep 11 '15 at 5:57
1

Finally I found the problem with the above code. I feel this as a bug in Magento. However you can judge my point after going through my answer.

Below is the important part from observer that does the forward trick.

$product = $collection->getFirstItem();
$actionName = 'view';
$controllerName = 'product';
$moduleName = 'catalog';
$params = array(
    'category' => $categoryId,
    'id'       => $product->getId()
);

//tells no to category page router processing further
$request->setDispatched(false);

//throw exception with a clear message we need a product page.
$e = new Mage_Core_Controller_Varien_Exception();
$e->prepareForward($actionName, $controllerName, $moduleName, $params);
throw $e;

When we throw an exception Mage_Core_Controller_Varien_Exception after do a proper prepareForward, then what will happen is, this will be eventually fire Mage_Core_Controller_Varien_Action::_forward() method, which you all know do a forward in Magento (you can treat a forward as a redirect with keeping the URL same). This froward() function will set parameters correctly to the request object. There for $request object will have

Mage_Core_Controller_Varien_Request_Http $request {
    $this->_params = array (
        'category' => 12
        'id'       => 448
    )
}

Here I am assuming categoryId = 12 and productId = 448

Everything fine upto this. Now router is starting to process the triggered forward that we have done right now. But standard router1 while doing matching2 will do an additional rewrite on parameters based on the path information. This is what make problem in this unique case.

public function match(Zend_Controller_Request_Http $request)
{
    .....
    // set values only after all the checks are done
    $request->setModuleName($module);
    $request->setControllerName($controller);
    $request->setActionName($action);
    $request->setControllerModule($realModule);

    // set parameters from pathinfo
    for ($i = 3, $l = sizeof($p); $i < $l; $i += 2) {
        $request->setParam($p[$i], isset($p[$i+1]) ? urldecode($p[$i+1]) : '');
    }
    ....
}

In our case path info ($p) is catalog/category/view/id/123. Due to this it will rewrite the param id and hence $request now becomes

Mage_Core_Controller_Varien_Request_Http $request {
    $this->_params = array (
        'category' => 12
        'id'       => 12
    )
}

Due to this in Mage_Catalog_ProductController::view() action trighter noRoute action since it can't find a valid product id.

Solution

Solution for this unique problem is pretty simple, change the pathInfo of the request before we throw exception in our observer. You can set path info like this.

$pathInfo = 'catalog/product/view/id' . $product->getId();
$request->setPathInfo($pathInfo);

1 : Mage_Core_Controller_Varien_Router_Standard

2 : Refers to Mage_Core_Controller_Varien_Router_Standard::match(). This is the place where controller method is get triggered.

3 : pathInfo will not change when we do a fowrard. It will remain same.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.