How to add custom product relations in Magento

Inchoo_CustomLinkedProducts

Currently there are three types of product relations in Magento: Up-sells, Related Products, and Cross-sell Products. Recently while discussing client’s requirements, we’ve come to conclusion it would serve our purpose best to link products in the same way Magento does, and then use our own relations independently of Magento’s built in product relations. In this article I’ll show you how to add custom product relations as addition to existing Magento product relations.

Introduction

Inside following paragraphs I’ll do my best to present code snippets from Magento extension created to illustrate process of adding custom product relations in Magento. Full source code for the extension named Inchoo_CustomLinkedProducts is available from its GitHub repository page.

Installer script

First thing we need to do is notify Magento database about out new product link type. We will do this using following installer script:

app/code/community/Inchoo/CustomLinkedProducts/sql/inchoo_customlinkedproducts_setup/install-0.0.1.php

<?php
 
$installer = $this;
 
/**
 * Install product link types
 */
$data = array(
    array(
        'link_type_id'  => Inchoo_CustomLinkedProducts_Model_Catalog_Product_Link::LINK_TYPE_CUSTOM,
        'code'          => 'custom'
    )
);
 
foreach ($data as $bind) {
    $installer->getConnection()->insertForce($installer->getTable('catalog/product_link_type'), $bind);
}
 
/**
 * Install product link attributes
 */
$data = array(
    array(
        'link_type_id'                  => Inchoo_CustomLinkedProducts_Model_Catalog_Product_Link::LINK_TYPE_CUSTOM,
        'product_link_attribute_code'   => 'position',
        'data_type'                     => 'int'
    )
);
 
$installer->getConnection()->insertMultiple($installer->getTable('catalog/product_link_attribute'), $data);

Admin interface

Next thing is to add a new tab to Product Information screen at admin side. First here’s the PHP code, later we will add it to layout using our layout XML file:

app/code/community/Inchoo/CustomLinkedProducts/Block/Adminhtml/Catalog/Product/Edit/Tab.php

<?php
 
class Inchoo_CustomLinkedProducts_Block_Adminhtml_Catalog_Product_Edit_Tab
extends Mage_Adminhtml_Block_Widget
implements Mage_Adminhtml_Block_Widget_Tab_Interface
{
    public function canShowTab() 
    {
        return true;
    }
 
    public function getTabLabel() 
    {
        return $this->__('Custom Linked Products');
    }
 
    public function getTabTitle()        
    {
        return $this->__('Custom Linked Products');
    }
 
    public function isHidden()
    {
        return false;
    }
 
    public function getTabUrl() 
    {
        return $this->getUrl('*/*/custom', array('_current' => true));
    }
 
    public function getTabClass()
    {
        return 'ajax';
    }
 
}

Since our tab points to ‘*/*/custom’ route, we need to add it to Mage_Adminhtml_Catalog_ProductController by rewriting it:

app/code/community/Inchoo/CustomLinkedProducts/controllers/Adminhtml/Catalog/ProductController.php

<?php
 
require_once(Mage::getModuleDir('controllers','Mage_Adminhtml').DS.'Catalog'.DS.'ProductController.php');
 
class Inchoo_CustomLinkedProducts_Adminhtml_Catalog_ProductController extends Mage_Adminhtml_Catalog_ProductController
{
    /**
     * Get custom products grid and serializer block
     */
    public function customAction()
    {
        $this->_initProduct();
        $this->loadLayout();
        $this->getLayout()->getBlock('catalog.product.edit.tab.custom')
            ->setProductsCustom($this->getRequest()->getPost('products_custom', null));
        $this->renderLayout();
    }
 
    /**
     * Get custom products grid
     */
    public function customGridAction()
    {
        $this->_initProduct();
        $this->loadLayout();
        $this->getLayout()->getBlock('catalog.product.edit.tab.custom')
            ->setProductsRelated($this->getRequest()->getPost('products_custom', null));
        $this->renderLayout();
    }
 
}

To add our new tab to Product Information screen and for getBlock(‘catalog.product.edit.tab.custom’) to work on both customAction() and customGridAction(), we also need to add a bit of XML to our layout:

app/design/adminhtml/default/default/layout/inchoo_customlinkedproducts.xml

<?xml version="1.0" encoding="UTF-8"?>
 
<layout>
 
    <adminhtml_catalog_product_edit>
        <reference name="product_tabs">
            <action method="addTab">
                <name>custom</name>
                <block>inchoo_customlinkedproducts/adminhtml_catalog_product_edit_tab</block>
            </action>
        </reference>
    </adminhtml_catalog_product_edit>
 
    <adminhtml_catalog_product_custom>
        <block type="core/text_list" name="root" output="toHtml">
            <block type="inchoo_customlinkedproducts/adminhtml_catalog_product_edit_tab_custom" name="catalog.product.edit.tab.custom"/>
            <block type="adminhtml/widget_grid_serializer" name="custom_grid_serializer">
                <reference name="custom_grid_serializer">
                    <action method="initSerializerBlock">
                        <grid_block_name>catalog.product.edit.tab.custom</grid_block_name>
                        <data_callback>getSelectedCustomProducts</data_callback>
                        <hidden_input_name>links[custom]</hidden_input_name>
                        <reload_param_name>products_custom</reload_param_name>
                    </action>
                    <action method="addColumnInputName">
                        <input_name>position</input_name>
                    </action>
                </reference>
            </block>
        </block>
    </adminhtml_catalog_product_custom>
 
    <adminhtml_catalog_product_customgrid>
        <block type="core/text_list" name="root" output="toHtml">
            <block type="inchoo_customlinkedproducts/adminhtml_catalog_product_edit_tab_custom" name="catalog.product.edit.tab.custom"/>
        </block>
    </adminhtml_catalog_product_customgrid>
 
</layout>

Next thing, we need to create grid to be hosted inside our newly created Custom Linked Products tab. There’s a lot if code for this task, but much of it is pretty generic grid code. The one thing you’ll notice is call to useCustomLinks() inside _prepareCollection() function. Here’s the excerpt from our grid class:

app/code/community/Inchoo/CustomLinkedProducts/Block/Adminhtml/Catalog/Product/Edit/Tab/Custom.php

<?php
$collection = Mage::getModel('catalog/product_link')->useCustomLinks()
    ->getProductCollection()
    ->setProduct($this->_getProduct())
    ->addAttributeToSelect('*');

So this is how interface for managing Custom Linked Products looks like inside Magento admin:

Inchoo_Customlinkedproducts admin

Backend supporting code

As you might have noticed, function useCustomLinks() doesn’t exist inside catalog/product_link model, thus we need to rewrite this model to include our custom product link specific functionality:

app/code/community/Inchoo/CustomLinkedProducts/Model/Catalog/Product/Link.php

<?php
 
class Inchoo_CustomLinkedProducts_Model_Catalog_Product_Link extends Mage_Catalog_Model_Product_Link
{
    const LINK_TYPE_CUSTOM   = 6;
 
    /**
     * @return Mage_Catalog_Model_Product_Link
     */
    public function useCustomLinks()
    {
        $this->setLinkTypeId(self::LINK_TYPE_CUSTOM);
        return $this;
    }
 
    /**
     * Save data for product relations
     *
     * @param   Mage_Catalog_Model_Product $product
     * @return  Mage_Catalog_Model_Product_Link
     */
    public function saveProductRelations($product)
    {
        parent::saveProductRelations($product);
 
        $data = $product->getCustomLinkData();
        if (!is_null($data)) {
            $this->_getResource()->saveProductLinks($product, $data, self::LINK_TYPE_CUSTOM);
        }
 
    }
}

If you were to add another custom relation to same Magento installation, you would need to increase LINK_TYPE_CUSTOM class constant to avoid conflicts. Now you might notice that our catalog/product model doesn’t have getCustomLinkData() function. Fear not, another rewrite to the rescue:

app/code/community/Inchoo/CustomLinkedProducts/Model/Catalog/Product.php

<?php
 
class Inchoo_CustomLinkedProducts_Model_Catalog_Product extends Mage_Catalog_Model_Product
{
    /**
     * Retrieve array of custom products
     *
     * @return array
     */
    public function getCustomProducts()
    {
        if (!$this->hasCustomProducts()) {
            $products = array();
            $collection = $this->getCustomProductCollection();
            foreach ($collection as $product) {
                $products[] = $product;
            }
            $this->setCustomProducts($products);
        }
        return $this->getData('custom_products');
    }
 
    /**
     * Retrieve custom products identifiers
     *
     * @return array
     */
    public function getCustomProductIds()
    {
        if (!$this->hasCustomProductIds()) {
            $ids = array();
            foreach ($this->getCustomProducts() as $product) {
                $ids[] = $product->getId();
            }
            $this->setCustomProductIds($ids);
        }
        return $this->getData('custom_product_ids');
    }
 
    /**
     * Retrieve collection custom product
     *
     * @return Mage_Catalog_Model_Resource_Product_Link_Product_Collection
     */
    public function getCustomProductCollection()
    {
        $collection = $this->getLinkInstance()->useCustomLinks()
            ->getProductCollection()
            ->setIsStrongMode();
        $collection->setProduct($this);
        return $collection;
    }
 
    /**
     * Retrieve collection custom link
     *
     * @return Mage_Catalog_Model_Resource_Product_Link_Collection
     */
    public function getCustomLinkCollection()
    {
        $collection = $this->getLinkInstance()->useCustomLinks()
            ->getLinkCollection();
        $collection->setProduct($this);
        $collection->addLinkTypeIdFilter();
        $collection->addProductIdFilter();
        $collection->joinAttributes();
        return $collection;
    }
 
}

There’s one more crucial thing left to do. To enable our product relations save and duplicate functionality on catalog product, we must attach to catalog_product_prepare_save and catalog_model_product_duplicate events. Here’s the observer class with callbacks for these events:

<?php
 
class Inchoo_CustomLinkedProducts_Model_Observer extends Varien_Object
{
    public function catalogProductPrepareSave($observer)
    {
        $event = $observer->getEvent();
 
        $product = $event->getProduct();
        $request = $event->getRequest();
 
        $links = $request->getPost('links');
        if (isset($links['custom']) && !$product->getCustomReadonly()) {
            $product->setCustomLinkData(Mage::helper('adminhtml/js')->decodeGridSerializedInput($links['custom']));
        }
    }
 
    public function catalogModelProductDuplicate($observer)
    {
        $event = $observer->getEvent();
 
        $currentProduct = $event->getCurrentProduct();
        $newProduct = $event->getNewProduct();
 
        $data = array();
        $currentProduct->getLinkInstance()->useCustomLinks();
        $attributes = array();
        foreach ($currentProduct->getLinkInstance()->getAttributes() as $_attribute) {
            if (isset($_attribute['code'])) {
                $attributes[] = $_attribute['code'];
            }
        }
        foreach ($currentProduct->getCustomLinkCollection() as $_link) {
            $data[$_link->getLinkedProductId()] = $_link->toArray($attributes);
        }
        $newProduct->setCustomLinkData($data);
    }
 
}

Frontend interface

To demonstrate our custom product relation on Magento frontend, I’ve cloned Magento’s related products block inside inchoo_customrelatedproducts/catalog_product_list_custom block together with related template file. Our Related Products block clone will use our custom product relations instead of builtin Related Products relations. This is how it looks like in frontend:

Inchoo_Customlinkedproducts frontend

I hope you’ll find some use for this code inside your own projects. Like always if you have any questions please leave your comment here and I’ll respond as soon as possible. If you’ve noticed some bug or wish to improve this code in any way, feel free to fork Inchoo_CustomLinkedProducts at GitHub.


64 comments

  1. I downloaded this and installed. How do I get it to how up on the product page (view.phtml)? I am using CE 1.9. Thanks!

  2. Thanks for this extension, it works well ansd is just what we needed.
    One question however. Is it possible to import custom related products by a CSV file, and than using Magmi for the import in particular. If the custom related products can be imported by CSV in Magmi, how should the column be called? Thanks in advance!

  3. Hmm, having two custom product relations doesn’t work right, as in when your modifying the product and clicking on the tab, nothing loads. I’ve duplicated the code and changed the namespace and changed const LINK_TYPE_BLAHBA to a higher number.
    However when I disabled the initial custom product relations from etc config file, the tab works within the product creation/modification screen. I think there is a conflict loading the grid.

  4. Hey,
    extension works perfect. Is it possible to add an attribute to the generated tab above the product grid via installer script?
    Thanks in advance!

  5. thanks .It works perfect.
    How can i add another relation??do i need to clone entire module and then changing namespaces??

  6. Does not work on frontend in magento 1.9.1.0 nor in 1.9.2.4
    Can you explain how to get it to work in frontend please?

  7. Hi
    I have installed this extension, Its working fine in backend but its not working on frontend. When I add a product to cart, its not adding custom linked products to cart.
    Thanks

  8. Hi , I have used your module , it is worked perfect in backend , but it is not proper working in front-end
    –> so any one help me how to show this product in frond-end …… ?
    –> When i try to direct call phtml file then this time it’s give me error for get_item_size() = 0, that means there is not select any product.
    So plz help me how to show this extension product in frond-end ……?

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.