Recently, one of my blog readers has raised a question in the comments to my post Magento Grouped Poduct Type: Is there a way to direct a customer to the grouped product when he clicks a link to the associated simple product?
The answer is, of course, yes. For this task I’ve proposed to use the Observer Pattern, which I have already covered in one of my tutorials. In this post, I am going to present you a simple extension that performs the grouped product redirect using an observer.
The directory structure I need is simple:
app/ code/ community/ Solvingmagento/ GroupedRedirect/ Model/ etc/
As you can see, I will only need a Model directory to house the observer class file and a standard etc folder for the configuration files. The module registration file has the following content:
<?xml version="1.0"?> <config> <modules> <Solvingmagento_GroupedRedirect> <active>true</active> <codePool>community</codePool> </Solvingmagento_GroupedRedirect> </modules> </config>
Listing 1. The registration file for the Solvingmagento_GroupedRedirect extension, /app/etc/modules/Solvingmagento_GroupedRedirect.xml, line 1.
The name of the new extension is GroupedRedirect and it belongs to the Solvingmagento package in the community code pool.
How am I going to redirect the associated products to their grouped parent? Simple:
- Listen to an event which is fired when a product detail page is opening,
- Detect if the product has a parent
- Check if the parent is of the Grouped product type
- Redirect, if yes.
The event in question is fired from the initProduct function of the Mage_Catalog_Helper_Product class. This function is called when Magento dispatches a request for a product detail page to the product controller. This functions receives a product ID, loads the product model and, if everything was OK, fires an event catalog_controller_product_init_after. My observer will capture this event and check if a redirect is necessary.
The observer class, Solvingmagento_GroupedRedirect_Model_Observer, contains a single function: redirectGrouped. The function’s code is this:
public function redirectGrouped(Varien_Event_Observer $observer) { $product = $observer->getEvent()->getProduct(); $groupedTypeInstance = Mage::getModel('catalog/product_type_grouped'); $parentIds = $groupedTypeInstance->getParentIdsByChild($product->getId()); foreach ($parentIds as $parentId) { $parent = Mage::getModel('catalog/product')->load($parentId); if ($parent && $parent instanceof Mage_Catalog_Model_Product && $parent->getTypeId() == 'grouped' ) { $redirect = Mage::getStoreConfig( 'catalog/grouped_options/redirect_enabled', Mage::app()->getStore()->getId() ); if ($redirect) { Mage::app()->getResponse()->setRedirect($parent->getProductUrl()); } break; } } }
Listing 2. Function redirectGrouped, /app/code/community/Solvingmagento/GroupedRedirect/Model/Observer.php, line 35.
The redirectGrouped function receives an object of type Varien_Event_Observer, which contains a product object. The $groupedTypeInstance variable is an instance of the Grouped Product Type model that has a useful function getParentIdsByChild. This function returns an array of IDs of all parent products if the current product has any. The redirectGrouped function loops through the parent product IDs array and loads every parent product candidate. If the loaded parent product is valid and is of type Grouped, the redirectGrouped function fetches its URL and performs the redirect.
You can see in the loop that my code checks a configuration setting catalog/grouped_options/redirect_enabled. This is a path to a back-end configuration option that allows enabling or disabling of the grouped product redirect. This option is defined in the system.xml file in the etc folder:
<config> <sections> <catalog> <groups> <grouped_options translate="label"> <label>Grouped Product Options</label> <frontend_type>text</frontend_type> <sort_order>600</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> <fields> <redirect_enabled translate="label"> <label>Enable Grouped Redirect</label> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_yesno</source_model> <sort_order>6</sort_order> <show_in_default>1</show_in_default> <show_in_website>1</show_in_website> <show_in_store>1</show_in_store> </redirect_enabled> </fields> </grouped_options> </groups> </catalog> </sections> </config>
Listing 3. Back-end option configuration, /app/code/community/Solvingmagento/GroupedRedirect/etc/system.xml, line 16.
The code in Listing 3 places the configuration option redirect_enabled into a new option group grouped_options created in the catalog section. The redirect_enabled can be set to yes or no.
My extension is still missing its config.xml file where I must declare a path to my observer and configure the event capture. The necessary code is this:
<config> <modules> <Solvingmagento_GroupedRedirect> <version>0.1.0</version> </Solvingmagento_GroupedRedirect> </modules> <global> <models> <solvingmagento_groupedredirect> <class>Solvingmagento_GroupedRedirect_Model</class> </solvingmagento_groupedredirect> </models> <events> <catalog_controller_product_init_after> <observers> <solvingmagento_groupedredirect> <class>solvingmagento_groupedredirect/observer</class> <method>redirectGrouped</method> </solvingmagento_groupedredirect> </observers> </catalog_controller_product_init_after> </events> </global> <default> <catalog> <grouped_options> <redirect_enabled>1</redirect_enabled> </grouped_options> </catalog> </default> </config>
Listing 4. Extension configuration, /app/code/community/Solvingmagento/GroupedRedirect/etc/config.xml, line 16.
The code above declares the location of my model files (the observer class file) under the global > models > solvingmagento_groupedredirect path. The <events> node declares an observer class to capture the catalog_controller_product_init_after using the redirectGrouped method. Finally, I declare a default value for the grouped_options option: yes.
This is it: the finished extension will redirect every associated product to its grouped parent. You can download the code from GitHub: Solvingmagento_GroupedRedirect.
Hi Oleg,
This tutorial is the very good!
Concise and clear, the best I have ever worked from and the solution worked first time.
Thank-you
Hello again Oleg,
I just noticed one very minor thing;
I assumed as the grouped product was associated to a category the re-direct would now show the breadcrumb trail but it does not.
It still show no breadcrumbs as it did with the simple product not placed in a category.
Any thoughts on this?
Thanks, Dan
Hi Daniel,
I think, I’ve figured out why the breadcrumbs don’t work.
The breadcrumb display of the category path depends on the Magento registry value current_category which is set in the same function Mage_Catalog_Helper_Product::initProduct whose event my extension uses to redirect the product page. When the browser requests a simple product’s URL, this function is first invoked for the simple product. If the simple product is assigned to a category, this category’s ID is assigned to the current_category registry value. Later this value is used by the URL Rewrite system to return the grouped product URL containing both references to the product and the category. If the simple product has no parent category, the current_category will remain empty.
If Magento doesn’t know which category to use, it won’t be able to find the category path when my Observer function requests the URL of the grouped product. The URL rewrite then will return the URL with the product name only. Without the category parameter in the URL the breadcrumb helper won’t display the category.
Unfortunately I can’t offer a solution to this problem. Products in Magento can be assigned to multiple categories and Magento has to have the category context when generating product URLs. In my sample extension this context is provided by the simple product. If the simple product is not assigned to any category, there is no context. You’ll have to figure out a way to tell Magento what the category the grouped product is in.
Hi Oleg,
Thanks again for your detailed explanation.
I have solved the breadcrumbs showing when following the simple product path by placing the simple product into a category but changing the product visibility to search only.
Great! I’m happy this post was helpful for you.
Note that this code does not work properly if the simple product has more than one parent. For instance we have small add-on accessory which is a hidden simple product. It itself is part of a group item for the accessory. But the simple product is also listed on many other grouped products. So your code above, the logic that the first parent ID that is a grouped item is the proper parent is not always true.
Unfortunately, we like the ease of adding the accessory to the cart at the same time as the main product so we will be adding a “parent ID” attribute to the simple items which will point to the correct grouped item. The code above could easily be modified for this operation. The downside is more work maintaining the catalog, but seems like the only way.
Hi Oleg,
great idea with your extension but one tweak. Your showing in Listing 2 that your are loading a catalog/product within a foreach. This is an anti-pattern. The refactored version can look like:
A simple product can have multiple parent products … so you need somehow to check which parent product the customer really wants…
Also for checking types please use always the === which is more secure.
Cheers Cyrill
Thanks Cyrill,
This is a great improvement!