Now i create product edit page Frequently Bought Together Products tab and add Frequently Bought Together button click product sidebar open and display product grid add select product so it's work but issue selected product save button click then database table save but Frequently Bought Together Products tab grid not display. i think modifyData() function issue.
so please advance thanks for help
app/code/vendor/BoughtTogether/Ui/DataProvider/Product/Form/Modifier/Bought.php
<?php
namespace vendor\BoughtTogether\Ui\DataProvider\Product\Form\Modifier;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\Data\ProductLinkInterface;
use Magento\Catalog\Api\ProductLinkRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Eav\Api\AttributeSetRepositoryInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Phrase;
use Magento\Framework\UrlInterface;
use Magento\Ui\Component\DynamicRows;
use Magento\Ui\Component\Form\Element\DataType\Number;
use Magento\Ui\Component\Form\Element\DataType\Text;
use Magento\Ui\Component\Form\Element\Input;
use Magento\Ui\Component\Form\Field;
use Magento\Ui\Component\Form\Fieldset;
use Magento\Ui\Component\Modal;
use Magento\Catalog\Helper\Image as ImageHelper;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
class Bought extends \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Related
{
const DATA_SCOPE = '';
const DATA_SCOPE_BOUGHTTYPE = 'boughttype';
const GROUP_BOUGHTTYPE = 'boughttype';
/**
* @var string
*/
private static $previousGroup = 'search-engine-optimization';
/**
* @var int
*/
private static $sortOrder = 90;
/**
* @var LocatorInterface
* @since 101.0.0
*/
protected $locator;
/**
* @var UrlInterface
* @since 101.0.0
*/
protected $urlBuilder;
/**
* @var ProductLinkRepositoryInterface
* @since 101.0.0
*/
protected $productLinkRepository;
/**
* @var ProductRepositoryInterface
* @since 101.0.0
*/
protected $productRepository;
/**
* @var ImageHelper
* @since 101.0.0
*/
protected $imageHelper;
/**
* @var Status
* @since 101.0.0
*/
protected $status;
/**
* @var AttributeSetRepositoryInterface
* @since 101.0.0
*/
protected $attributeSetRepository;
/**
* @var string
* @since 101.0.0
*/
protected $scopeName;
/**
* @var string
* @since 101.0.0
*/
protected $scopePrefix;
protected $plCollection;
/**
* @var \Magento\Catalog\Ui\Component\Listing\Columns\Price
*/
private $priceModifier;
/**
* @param LocatorInterface $locator
* @param UrlInterface $urlBuilder
* @param ProductLinkRepositoryInterface $productLinkRepository
* @param ProductRepositoryInterface $productRepository
* @param ImageHelper $imageHelper
* @param Status $status
* @param AttributeSetRepositoryInterface $attributeSetRepository
* @param string $scopeName
* @param string $scopePrefix
*/
public function __construct(
\Magento\Catalog\Model\ResourceModel\Product\Link\CollectionFactory $plCollection,
LocatorInterface $locator,
UrlInterface $urlBuilder,
ProductLinkRepositoryInterface $productLinkRepository,
ProductRepositoryInterface $productRepository,
ImageHelper $imageHelper,
Status $status,
AttributeSetRepositoryInterface $attributeSetRepository,
$scopeName = '',
$scopePrefix = ''
) {
$this->_plCollection = $plCollection;
$this->locator = $locator;
$this->urlBuilder = $urlBuilder;
$this->productLinkRepository = $productLinkRepository;
$this->productRepository = $productRepository;
$this->imageHelper = $imageHelper;
$this->status = $status;
$this->attributeSetRepository = $attributeSetRepository;
$this->scopeName = $scopeName;
$this->scopePrefix = $scopePrefix;
}
/**
* {@inheritdoc}
*/
public function modifyMeta(array $meta)
{
$meta = array_replace_recursive(
$meta,
[
static::GROUP_BOUGHTTYPE => [
'children' => [
$this->scopePrefix . static::DATA_SCOPE_BOUGHTTYPE => $this->getBoughtTypeFieldset()
],
'arguments' => [
'data' => [
'config' => [
'label' => __('Frequently Bought Together Products'),
'collapsible' => true,
'componentType' => Fieldset::NAME,
'dataScope' => static::DATA_SCOPE,
'sortOrder' =>
$this->getNextGroupSortOrder(
$meta,
self::$previousGroup,
self::$sortOrder
),
],
],
],
],
]
);
return $meta;
}
/**
* {@inheritdoc}
* @since 101.0.0
*/
public function modifyData(array $data)
{
/** @var \Magento\Catalog\Model\Product $product */
$product = $this->locator->getProduct();
$productId = $product->getId();
if (!$productId) {
return $data;
}
$link_type_id = 6;
$plCollection = $this->_plCollection->create();
$plCollection->addFieldToFilter('product_id',$productId);
$plCollection->addFieldToFilter('link_type_id',$link_type_id);
$availableProduct = $plCollection->getColumnValues('linked_product_id');
//print_r($availableProduct);
/* echo "<pre>";
print_r($plCollection->getData());
echo "</pre>";
*/
$priceModifier = $this->getPriceModifier();
/**
* Set field name for modifier
*/
$priceModifier->setData('name', 'price');
foreach ($this->getDataScopes() as $dataScope) {
$data[$productId]['links'][$dataScope] = [];
foreach ($this->productLinkRepository->getList($product) as $linkItem) {
if ($linkItem->getLinkType() !== $dataScope) {
continue;
}
/** @var \Magento\Catalog\Model\Product $linkedProduct */
$linkedProduct = $this->productRepository->get(
$linkItem->getLinkedProductSku(),
false,
$this->locator->getStore()->getId()
);
$data[$productId]['links'][$dataScope][] = $this->fillData($linkedProduct, $linkItem);
}
if (!empty($data[$productId]['links'][$dataScope])) {
$dataMap = $priceModifier->prepareDataSource([
'data' => [
'items' => $data[$productId]['links'][$dataScope]
]
]);
$data[$productId]['links'][$dataScope] = $dataMap['data']['items'];
}
}
$data[$productId][self::DATA_SOURCE_DEFAULT]['current_product_id'] = $productId;
$data[$productId][self::DATA_SOURCE_DEFAULT]['current_store_id'] = $this->locator->getStore()->getId();
return $data;
}
/**
* Get price modifier
*
* @return \Magento\Catalog\Ui\Component\Listing\Columns\Price
* @deprecated 101.0.0
*/
private function getPriceModifier()
{
if (!$this->priceModifier) {
$this->priceModifier = ObjectManager::getInstance()->get(
\Magento\Catalog\Ui\Component\Listing\Columns\Price::class
);
}
return $this->priceModifier;
}
/**
* Prepares config for the Custom type products fieldset
*
* @return array
*/
protected function getBoughtTypeFieldset()
{
$content = __(
'Bought Together products are shown to customers in addition to the item the customer is looking at.'
);
return [
'children' => [
'button_set' => $this->getButtonSet(
$content,
__('Add Bought Together Products'),
$this->scopePrefix . static::DATA_SCOPE_BOUGHTTYPE
),
'modal' => $this->getGenericModal(
__('Add Bought Together Products'),
$this->scopePrefix . static::DATA_SCOPE_BOUGHTTYPE
),
static::DATA_SCOPE_BOUGHTTYPE => $this->getGrid($this->scopePrefix . static::DATA_SCOPE_BOUGHTTYPE),
],
'arguments' => [
'data' => [
'config' => [
'additionalClasses' => 'admin__fieldset-section',
'label' => __('Frequently Bought Together Products'),
'collapsible' => false,
'componentType' => Fieldset::NAME,
'dataScope' => '',
'sortOrder' => 90,
],
],
]
];
}
/**
* Prepare data column
*
* @param ProductInterface $linkedProduct
* @param ProductLinkInterface $linkItem
* @return array
* @since 101.0.0
*/
protected function fillData(ProductInterface $linkedProduct, ProductLinkInterface $linkItem)
{
return [
'id' => $linkedProduct->getId(),
'thumbnail' => $this->imageHelper->init($linkedProduct, 'product_listing_thumbnail')->getUrl(),
'name' => $linkedProduct->getName(),
'status' => $this->status->getOptionText($linkedProduct->getStatus()),
'attribute_set' => $this->attributeSetRepository
->get($linkedProduct->getAttributeSetId())
->getAttributeSetName(),
'sku' => $linkItem->getLinkedProductSku(),
'price' => $linkedProduct->getPrice(),
'position' => $linkItem->getPosition(),
];
}
/**
* Retrieve all data scopes
*
* @return array
*/
protected function getDataScopes()
{
return [
static::DATA_SCOPE_BOUGHTTYPE
];
}
/**
* Retrieve button set
*
* @param Phrase $content
* @param Phrase $buttonTitle
* @param string $scope
* @return array
* @since 101.0.0
*/
protected function getButtonSet(Phrase $content, Phrase $buttonTitle, $scope)
{
$modalTarget = $this->scopeName . '.' . static::GROUP_BOUGHTTYPE . '.' . $scope . '.modal';
return [
'arguments' => [
'data' => [
'config' => [
'formElement' => 'container',
'componentType' => 'container',
'label' => false,
'content' => $content,
'template' => 'ui/form/components/complex',
],
],
],
'children' => [
'button_' . $scope => [
'arguments' => [
'data' => [
'config' => [
'formElement' => 'container',
'componentType' => 'container',
'component' => 'Magento_Ui/js/form/components/button',
'actions' => [
[
'targetName' => $modalTarget,
'actionName' => 'toggleModal',
],
[
'targetName' => $modalTarget . '.' . $scope . '_product_listing',
'actionName' => 'render',
]
],
'title' => $buttonTitle,
'provider' => null,
],
],
],
],
],
];
}
/**
* Prepares config for modal slide-out panel
*
* @param Phrase $title
* @param string $scope
* @return array
* @since 101.0.0
*/
protected function getGenericModal(Phrase $title, $scope)
{
$listingTarget = $scope . '_product_listing';
$modal = [
'arguments' => [
'data' => [
'config' => [
'componentType' => Modal::NAME,
'dataScope' => '',
'options' => [
'title' => $title,
'buttons' => [
[
'text' => __('Cancel'),
'actions' => [
'closeModal'
]
],
[
'text' => __('Add Selected Products'),
'class' => 'action-primary',
'actions' => [
[
'targetName' => 'index = ' . $listingTarget,
'actionName' => 'save'
],
'closeModal'
]
],
],
],
],
],
],
'children' => [
$listingTarget => [
'arguments' => [
'data' => [
'config' => [
'autoRender' => false,
'componentType' => 'insertListing',
'dataScope' => $listingTarget,
'externalProvider' => $listingTarget . '.' . $listingTarget . '_data_source',
'selectionsProvider' => $listingTarget . '.' . $listingTarget . '.product_columns.ids',
'ns' => $listingTarget,
'render_url' => $this->urlBuilder->getUrl('mui/index/render'),
'realTimeLink' => true,
'dataLinks' => [
'imports' => false,
'exports' => true
],
'behaviourType' => 'simple',
'externalFilterMode' => true,
'imports' => [
'productId' => '${ $.provider }:data.product.current_product_id',
'storeId' => '${ $.provider }:data.product.current_store_id',
],
'exports' => [
'productId' => '${ $.externalProvider }:params.current_product_id',
'storeId' => '${ $.externalProvider }:params.current_store_id',
]
],
],
],
],
],
];
return $modal;
}
/**
* Retrieve grid
*
* @param string $scope
* @return array
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @since 101.0.0
*/
protected function getGrid($scope)
{
$dataProvider = $scope . '_product_listing';
return [
'arguments' => [
'data' => [
'config' => [
'additionalClasses' => 'admin__field-wide',
'componentType' => DynamicRows::NAME,
'label' => null,
'columnsHeader' => false,
'columnsHeaderAfterRender' => true,
'renderDefaultRecord' => false,
'template' => 'ui/dynamic-rows/templates/grid',
'component' => 'Magento_Ui/js/dynamic-rows/dynamic-rows-grid',
'addButton' => false,
'recordTemplate' => 'record',
'dataScope' => 'data.links',
'deleteButtonLabel' => __('Remove'),
'dataProvider' => $dataProvider,
'map' => [
'id' => 'entity_id',
'name' => 'name',
'status' => 'status_text',
'attribute_set' => 'attribute_set_text',
'sku' => 'sku',
'price' => 'price',
'thumbnail' => 'thumbnail_src',
],
'links' => [
'insertData' => '${ $.provider }:${ $.dataProvider }'
],
'sortOrder' => 2,
],
],
],
'children' => [
'record' => [
'arguments' => [
'data' => [
'config' => [
'componentType' => 'container',
'isTemplate' => true,
'is_collection' => true,
'component' => 'Magento_Ui/js/dynamic-rows/record',
'dataScope' => '',
],
],
],
'children' => $this->fillMeta(),
],
],
];
}
/**
* Retrieve meta column
*
* @return array
* @since 101.0.0
*/
protected function fillMeta()
{
return [
'id' => $this->getTextColumn('id', false, __('ID'), 0),
'thumbnail' => [
'arguments' => [
'data' => [
'config' => [
'componentType' => Field::NAME,
'formElement' => Input::NAME,
'elementTmpl' => 'ui/dynamic-rows/cells/thumbnail',
'dataType' => Text::NAME,
'dataScope' => 'thumbnail',
'fit' => true,
'label' => __('Thumbnail'),
'sortOrder' => 10,
],
],
],
],
'name' => $this->getTextColumn('name', false, __('Name'), 20),
'status' => $this->getTextColumn('status', true, __('Status'), 30),
'attribute_set' => $this->getTextColumn('attribute_set', false, __('Attribute Set'), 40),
'sku' => $this->getTextColumn('sku', true, __('SKU'), 50),
'price' => $this->getTextColumn('price', true, __('Price'), 60),
'actionDelete' => [
'arguments' => [
'data' => [
'config' => [
'additionalClasses' => 'data-grid-actions-cell',
'componentType' => 'actionDelete',
'dataType' => Text::NAME,
'label' => __('Actions'),
'sortOrder' => 70,
'fit' => true,
],
],
],
],
'position' => [
'arguments' => [
'data' => [
'config' => [
'dataType' => Number::NAME,
'formElement' => Input::NAME,
'componentType' => Field::NAME,
'dataScope' => 'position',
'sortOrder' => 80,
'visible' => false,
],
],
],
],
];
}
/**
* Retrieve text column structure
*
* @param string $dataScope
* @param bool $fit
* @param Phrase $label
* @param int $sortOrder
* @return array
* @since 101.0.0
*/
protected function getTextColumn($dataScope, $fit, Phrase $label, $sortOrder)
{
$column = [
'arguments' => [
'data' => [
'config' => [
'componentType' => Field::NAME,
'formElement' => Input::NAME,
'elementTmpl' => 'ui/dynamic-rows/cells/text',
'component' => 'Magento_Ui/js/form/element/text',
'dataType' => Text::NAME,
'dataScope' => $dataScope,
'fit' => $fit,
'label' => $label,
'sortOrder' => $sortOrder,
],
],
],
];
return $column;
}
}
Database Table:
(1) catalog_product_link table images
(2) catalog_product_link_type table