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"