In this post, we will check how to add custom link products like related products, Up-sell products and Cross-sell products. We will add new product type `customlink` (Buy together product) from admin Just below the Related, Up-sell and Cross-sell product tab.
Now, to create a custom product link type, we need to add its entry in catalog_product_link_type and catalog_product_link_attribute tables, then create modifier (to add it on product edit page), model (to get linked product collection from catalog product model), and ui_component.It Also supports to import `custom link products` via Magento's Import CSV.
I have also added GrapQL Query to fetch the details of custom linked products.
You can find complete module in Github at Magelearn_LinkProduct
Let's start it by creating custom module.
Create a folder inside app/code/Magelearn/LinkProduct
Add registration.php file in it:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Magelearn_LinkProduct',
__DIR__
);Add composer.json file in it:
{
"name": "magelearn/link-product",
"description": "Custom Link product relation for magento 2",
"license": "proprietary",
"authors": [
{
"name": "vijay rami",
"email": "vijaymrami@gmail.com"
}
],
"require": {},
"type": "magento2-module",
"autoload": {
"files": [
"registration.php"
],
"psr-4": {
"Magelearn\\LinkProduct\\": ""
}
}
}Add etc/module.xml file in it:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Magelearn_LinkProduct" setup_version="1.0.0">
<sequence>
<module name="Magento_Catalog"/>
</sequence>
</module>
</config>First We will check how to add new product type in product edit form at admin inside Related Products tab.
For that first add etc/adminhtml/di.xml file.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
<arguments>
<argument name="modifiers" xsi:type="array">
<item name="customlink" xsi:type="array">
<item name="class" xsi:type="string">Magelearn\LinkProduct\Ui\DataProvider\Product\Form\Modifier\CustomLinkTab</item>
<item name="sortOrder" xsi:type="number">120</item>
</item>
</argument>
</arguments>
</virtualType>
</config>Now as per highlighted above add our custom product form modifier class at Ui/DataProvider/Product/Form/Modifier/CustomLinkTab.php
<?php
namespace Magelearn\LinkProduct\Ui\DataProvider\Product\Form\Modifier;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Related;
use Magento\Ui\Component\Form\Fieldset;
class CustomLinkTab extends Related
{
const DATA_SCOPE_CUSTOM = 'customlink';
/**
* @var string
*/
private static $previousGroup = 'search-engine-optimization';
/**
* @var int
*/
private static $sortOrder = 90;
/**
* {@inheritdoc}
*/
public function modifyMeta(array $meta)
{
$meta = array_replace_recursive(
$meta,
[
static::GROUP_RELATED => [
'children' => [
$this->scopePrefix . static::DATA_SCOPE_CUSTOM => $this->getCustomFieldset()
],
'arguments' => [
'data' => [
'config' => [
'label' => __('Related Products, Up-Sells, Cross-Sells and Buy together Products'),
'collapsible' => true,
'componentType' => Fieldset::NAME,
'dataScope' => static::DATA_SCOPE,
'sortOrder' => $this->getNextGroupSortOrder(
$meta,
self::$previousGroup,
self::$sortOrder
),
],
],
],
],
]
);
return $meta;
}
/**
* Prepares config for the Custom type products fieldset
*
* @return array
*/
protected function getCustomFieldset()
{
$content = __(
'Buy Together products are shown to customers in addition to the item the customer is looking at.'
);
return [
'children' => [
'button_set' => $this->getButtonSet(
$content,
__('Add Buy Together Products'),
$this->scopePrefix . static::DATA_SCOPE_CUSTOM
),
'modal' => $this->getGenericModal(
__('Add Buy Together Products'),
$this->scopePrefix . static::DATA_SCOPE_CUSTOM
),
static::DATA_SCOPE_CUSTOM => $this->getGrid($this->scopePrefix . static::DATA_SCOPE_CUSTOM),
],
'arguments' => [
'data' => [
'config' => [
'additionalClasses' => 'admin__fieldset-section',
'label' => __('Add Buy Together Products'),
'collapsible' => false,
'componentType' => Fieldset::NAME,
'dataScope' => '',
'sortOrder' => 90,
],
],
]
];
}
/**
* Retrieve all data scopes
*
* @return array
*/
protected function getDataScopes()
{
return [
static::DATA_SCOPE_CUSTOM
];
}
}As because of our Data Scope is 'customlink' we will add view/adminhtml/ui_component/customlink_product_listing.xml
<?xml version="1.0" encoding="UTF-8"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="provider" xsi:type="string">customlink_product_listing.customlink_product_listing_data_source</item>
<item name="deps" xsi:type="string">customlink_product_listing.customlink_product_listing_data_source</item>
</item>
<item name="spinner" xsi:type="string">product_columns</item>
</argument>
<dataSource name="customlink_product_listing_data_source">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="class" xsi:type="string">Magelearn\LinkProduct\Ui\DataProvider\Product\Related\CustomLinkDataProvider</argument>
<argument name="name" xsi:type="string">customlink_product_listing_data_source</argument>
<argument name="primaryFieldName" xsi:type="string">entity_id</argument>
<argument name="requestFieldName" xsi:type="string">id</argument>
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
<item name="update_url" xsi:type="url" path="mui/index/render"/>
<item name="storageConfig" xsi:type="array">
<item name="cacheRequests" xsi:type="boolean">false</item>
</item>
</item>
</argument>
</argument>
</dataSource>
<listingToolbar name="listing_top">
<filters name="listing_filters">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="statefull" xsi:type="array">
<item name="applied" xsi:type="boolean">false</item>
</item>
<item name="params" xsi:type="array">
<item name="filters_modifier" xsi:type="array" />
</item>
<item name="observers" xsi:type="array">
<item name="filters" xsi:type="object">Magento\Catalog\Ui\Component\Listing\Filters</item>
</item>
</item>
</argument>
</filters>
<paging name="listing_paging"/>
</listingToolbar>
<columns name="product_columns" class="Magento\Catalog\Ui\Component\Listing\Columns">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="childDefaults" xsi:type="array">
<item name="fieldAction" xsi:type="array">
<item name="provider" xsi:type="string">customLinkProductGrid</item>
<item name="target" xsi:type="string">selectProduct</item>
<item name="params" xsi:type="array">
<item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
</item>
</item>
</item>
</item>
</argument>
<selectionsColumn name="ids">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="indexField" xsi:type="string">entity_id</item>
<item name="sortOrder" xsi:type="number">0</item>
<item name="preserveSelectionsOnFilter" xsi:type="boolean">true</item>
</item>
</argument>
</selectionsColumn>
<column name="entity_id">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">textRange</item>
<item name="sorting" xsi:type="string">asc</item>
<item name="label" xsi:type="string" translate="true">ID</item>
<item name="sortOrder" xsi:type="number">10</item>
</item>
</argument>
</column>
<column name="thumbnail" class="Magento\Catalog\Ui\Component\Listing\Columns\Thumbnail">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/grid/columns/thumbnail</item>
<item name="add_field" xsi:type="boolean">true</item>
<item name="sortable" xsi:type="boolean">false</item>
<item name="altField" xsi:type="string">name</item>
<item name="has_preview" xsi:type="string">1</item>
<item name="align" xsi:type="string">left</item>
<item name="label" xsi:type="string" translate="true">Thumbnail</item>
<item name="sortOrder" xsi:type="number">20</item>
</item>
</argument>
</column>
<column name="name">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">text</item>
<item name="add_field" xsi:type="boolean">true</item>
<item name="label" xsi:type="string" translate="true">Name</item>
<item name="sortOrder" xsi:type="number">30</item>
</item>
</argument>
</column>
<column name="attribute_set_id">
<argument name="data" xsi:type="array">
<item name="options" xsi:type="object">Magento\Catalog\Model\Product\AttributeSet\Options</item>
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">select</item>
<item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item>
<item name="dataType" xsi:type="string">select</item>
<item name="label" xsi:type="string" translate="true">Attribute Set</item>
<item name="sortOrder" xsi:type="number">40</item>
</item>
</argument>
</column>
<column name="attribute_set_text" class="Magento\Catalog\Ui\Component\Listing\Columns\AttributeSetText">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="sortOrder" xsi:type="number">41</item>
<item name="label" xsi:type="string" translate="true">AttributeSetText</item>
<item name="visible" xsi:type="boolean">false</item>
</item>
</argument>
</column>
<column name="status">
<argument name="data" xsi:type="array">
<item name="options" xsi:type="object">Magento\Catalog\Model\Product\Attribute\Source\Status</item>
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">select</item>
<item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item>
<item name="dataType" xsi:type="string">select</item>
<item name="label" xsi:type="string" translate="true">Status</item>
<item name="sortOrder" xsi:type="number">50</item>
</item>
</argument>
</column>
<column name="status_text" class="Magento\Catalog\Ui\Component\Listing\Columns\StatusText">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="sortOrder" xsi:type="number">51</item>
<item name="label" xsi:type="string" translate="true">StatusText</item>
<item name="visible" xsi:type="boolean">false</item>
</item>
</argument>
</column>
<column name="type_id">
<argument name="data" xsi:type="array">
<item name="options" xsi:type="object">Magento\Catalog\Model\Product\Type</item>
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">select</item>
<item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item>
<item name="dataType" xsi:type="string">select</item>
<item name="label" xsi:type="string" translate="true">Type</item>
<item name="sortOrder" xsi:type="number">60</item>
</item>
</argument>
</column>
<column name="sku">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">SKU</item>
<item name="sortOrder" xsi:type="number">70</item>
</item>
</argument>
</column>
<column name="price" class="Magento\Catalog\Ui\Component\Listing\Columns\Price">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">textRange</item>
<item name="add_field" xsi:type="boolean">true</item>
<item name="label" xsi:type="string" translate="true">Price</item>
<item name="sortOrder" xsi:type="number">80</item>
</item>
</argument>
</column>
</columns>
</listing>
As per highlighted code above we will add our DataProvider class to display data properly on listing of this xml file.
Add Dataprovider class at Ui/DataProvider/Product/Related/CustomLinkDataProvider.php
<?php
namespace Magelearn\LinkProduct\Ui\DataProvider\Product\Related;
use Magento\Catalog\Ui\DataProvider\Product\Related\AbstractDataProvider;
class CustomLinkDataProvider extends AbstractDataProvider
{
/**
* {@inheritdoc
*/
protected function getLinkType()
{
return 'customlink';
}
}
After adding the above code new `customlink` product type will be displayed in the Product edit form below cross-sell products.
Now to create a custom product relation and save custom linked product types in database, we will add our install schema script.
This script will run only a single time during module installation command. Once the module will be installed, you need to remove module name (Magelearn_LinkProduct) entry from `seup_module` table to run it again.
Create Setup/InstallSchema.php file.
<?php
namespace Magelearn\LinkProduct\Setup;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magelearn\LinkProduct\Model\Product\Link;
use Magelearn\LinkProduct\Ui\DataProvider\Product\Form\Modifier\CustomLinkTab;
class InstallSchema implements InstallSchemaInterface
{
/**
* @param SchemaSetupInterface $setup
* @param ModuleContextInterface $context
*/
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
/**
* Install product link types in table (catalog_product_link_type)
*/
$catalogProductLinkTypeData = [
'link_type_id' => Link::LINK_TYPE_CUSTOMLINK,
'code' => CustomLinkTab::DATA_SCOPE_CUSTOM
];
$setup->getConnection()->insertOnDuplicate(
$setup->getTable('catalog_product_link_type'),
$catalogProductLinkTypeData
);
/**
* install product link attributes position in table catalog_product_link_attribute
*/
$catalogProductLinkAttributeData = [
'link_type_id' => Link::LINK_TYPE_CUSTOMLINK,
'product_link_attribute_code' => 'position',
'data_type' => 'int',
];
$setup->getConnection()->insert(
$setup->getTable('catalog_product_link_attribute'),
$catalogProductLinkAttributeData
);
$setup->endSetup();
}
}
Now, To create a custom product link, we need to inject some dependencies to core link provider classes, so create the following di.xml for that inside etc/di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Catalog\Model\Product\LinkTypeProvider">
<arguments>
<argument name="linkTypes" xsi:type="array">
<item name="customlink" xsi:type="const">Magelearn\LinkProduct\Model\Product\Link::LINK_TYPE_CUSTOMLINK</item>
</argument>
</arguments>
</type>
<type name="Magento\Catalog\Model\ProductLink\CollectionProvider">
<arguments>
<argument name="providers" xsi:type="array">
<item name="customlink" xsi:type="object">Magelearn\LinkProduct\Model\ProductLink\CollectionProvider\CustomLinkProducts</item>
</argument>
</arguments>
</type>
<!-- used for import custom link products via import CSV feature -->
<type name="Magento\CatalogImportExport\Model\Import\Product\LinkProcessor">
<arguments>
<argument name="linkNameToId" xsi:type="array">
<item name="_customlink_" xsi:type="const">Magelearn\LinkProduct\Model\Product\Link::LINK_TYPE_CUSTOMLINK</item>
</argument>
</arguments>
</type>
<type name="Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType">
<plugin sortOrder="1" name="magelearnLinkProductAbstractType" type="Magelearn\LinkProduct\Plugin\Model\Import\Product\Type\AbstractTypePlugin"/>
</type>
</config>
As per highlighted above code add our model class at Model/Product/Link.php
<?php
namespace Magelearn\LinkProduct\Model\Product;
class Link extends \Magento\Catalog\Model\Product\Link
{
const LINK_TYPE_CUSTOMLINK = 7;
/**
* @return $this
*/
public function useCustomLinks()
{
$this->setLinkTypeId(self::LINK_TYPE_CUSTOMLINK);
return $this;
}
}Add Model/ProductLink/CollectionProvider/CustomLinkProducts.php
<?php
namespace Magelearn\LinkProduct\Model\ProductLink\CollectionProvider;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\ProductLink\CollectionProviderInterface;
class CustomLinkProducts implements CollectionProviderInterface
{
/** @var \Magelearn\LinkProduct\Model\CustomLinkProduct */
protected $customLinkModel;
/**
* Custom Link constructor.
* @param \Magelearn\LinkProduct\Model\CustomLinkProduct $customLinkModel
*/
public function __construct(
\Magelearn\LinkProduct\Model\CustomLinkProduct $customLinkModel
) {
$this->customLinkModel = $customLinkModel;
}
/**
* {@inheritdoc}
*/
public function getLinkedProducts(Product $product)
{
return (array) $this->customLinkModel->getCustomLinkProducts($product);
}
}
Now as per highlighted code above add our custom link product model class at Magelearn/LinkProduct/Model/CustomLinkProduct.php
<?php
namespace Magelearn\LinkProduct\Model;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\ResourceModel\Product\Link\Collection;
use Magento\Framework\DataObject;
use Magelearn\LinkProduct\Model\Product\Link;
class CustomLinkProduct extends DataObject
{
/**
* Product link instance
*
* @var Product\Link
*/
protected $linkInstance;
/**
* CustomLinkProduct constructor.
* @param Link $productLink
*/
public function __construct(
Link $productLink
) {
$this->linkInstance = $productLink;
}
/**
* Retrieve link instance
*
* @return Product\Link
*/
public function getLinkInstance()
{
return $this->linkInstance;
}
/**
* Retrieve array of customlink products
*
* @param Product $currentProduct
* @return array
*/
public function getCustomLinkProducts(Product $currentProduct)
{
if (!$this->hasCustomLinkProducts()) {
$products = [];
$collection = $this->getCustomLinkProductCollection($currentProduct);
foreach ($collection as $product) {
$products[] = $product;
}
$this->setCustomLinkProducts($products);
}
return $this->getData('custom_link_products');
}
/**
* Retrieve customlink products identifiers
*
* @param Product $currentProduct
* @return array
*/
public function getCustomLinkProductIds(Product $currentProduct)
{
if (!$this->hasCustomLinkProductIds()) {
$ids = [];
foreach ($this->getCustomLinkProducts($currentProduct) as $product) {
$ids[] = $product->getId();
}
$this->setCustomLinkProductIds($ids);
}
return $this->getData('custom_link_product_ids');
}
/**
* Retrieve collection customlink product
*
* @param Product $currentProduct
* @return \Magento\Catalog\Model\ResourceModel\Product\Link\Product\Collection
*/
public function getCustomLinkProductCollection(Product $currentProduct)
{
$collection = $this->getLinkInstance()->useCustomLinks()->getProductCollection()->setIsStrongMode();
$collection->setProduct($currentProduct);
return $collection;
}
}
And at last as per highlighted in di.xml file, to support this custom product link type in import/export file, we will add our plugin class at Plugin/Model/Import/Product/Type/AbstractTypePlugin.php
<?php
namespace Magelearn\LinkProduct\Plugin\Model\Import\Product\Type;
use Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType;
class AbstractTypePlugin
{
/**
* @param AbstractType $subject
* @param string[] $result
* @return string[]
*/
public function afterGetCustomFieldsMapping(AbstractType $subject, array $result): array
{
$result['_customlink_sku'] = 'customlink_skus';
return $result;
}
}
For GraphQl
To get this custom link product types data in GraphQl, First add file etc/schema.graphqls
interface ProductInterface {
custom_link_products: [ProductInterface]
@doc(description: "An array of products to be displayed in a Buy Together Products block.")
@resolver(class: "Magelearn\\LinkProduct\\Model\\Resolver\\Batch\\BuyTogetherProducts")
}As per highlighted code above add resolver class at Model/Resolver/Batch/BuyTogetherProducts.php
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magelearn\LinkProduct\Model\Resolver\Batch;
use Magelearn\LinkProduct\Model\Product\Link;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\Resolver\BatchResponse;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\RelatedProductGraphQl\Model\Resolver\Batch\AbstractLikedProducts;
/**
* Buy Together Products Resolver
*/
class BuyTogetherProducts extends AbstractLikedProducts
{
/**
* @inheritDoc
*/
protected function getNode(): string
{
return 'custom_link_products';
}
/**
* @inheritDoc
*/
protected function getLinkType(): int
{
return Link::LINK_TYPE_CUSTOMLINK;
}
}
After adding above files, you can retrieve this custom type products data same as related products, up-sell products and cross-sell products data like below:
Just add `custom_link_products` in GraphQl Request.
Just add `custom_link_products` in GraphQl Request.
{
products(filter: { sku: { eq: "24-WB06" } }) {
items {
uid
name
related_products {
uid
name
}
upsell_products {
uid
name
}
crosssell_products {
uid
name
}
custom_link_products {
uid
name
}
}
}
}

0 Comments On "Create Custom Product Relation for Magento with Custom Linked Product Types with GraphQL Data"