├── Api
└── CustomEntityProductLinkManagementInterface.php
├── Block
└── Entity
│ └── Attribute
│ └── CustomEntity
│ └── Renderer.php
├── Helper
├── Data.php
└── Product.php
├── Model
├── CustomEntityProductLinkManagement.php
├── Entity
│ └── Attribute
│ │ ├── Frontend
│ │ └── CustomEntity.php
│ │ └── Source
│ │ └── CustomEntity.php
├── Layer
│ ├── CustomEntity.php
│ └── CustomEntity
│ │ └── CollectionFilter.php
└── Product
│ └── CustomEntity
│ └── ReadHandler.php
├── Observer
├── Adminhtml
│ ├── AddFieldsToAttributeObserver.php
│ ├── CatalogProductFormExcludedAttributesObserver.php
│ └── CustomEntityAttributeSaveBeforeObserver.php
└── Catalog
│ └── Product
│ ├── AddCustomEntitiesInformation.php
│ └── AddCustomEntityAttributeTypeObserver.php
├── Plugin
├── Block
│ └── Product
│ │ ├── ListProductPlugin.php
│ │ └── ViewPlugin.php
├── Catalog
│ ├── Controller
│ │ └── Adminhtml
│ │ │ └── Product
│ │ │ └── Initialization
│ │ │ └── HelperPlugin.php
│ └── Ui
│ │ └── DataProvider
│ │ └── Product
│ │ └── Form
│ │ └── Modifier
│ │ └── EavPlugin.php
├── Controller
│ └── Entity
│ │ └── ViewPlugin.php
└── Model
│ ├── Condition
│ └── Product
│ │ └── AbstractProduct.php
│ ├── ProductPlugin.php
│ └── ResourceModel
│ └── Eav
│ └── Attribute.php
├── Setup
└── Patch
│ └── Data
│ ├── MoveCustomEntityLink.php
│ └── UpdateProductCustomEntityAttribute.php
├── composer.json
├── etc
├── adminhtml
│ ├── di.xml
│ └── events.xml
├── di.xml
├── elasticsuite_indices.xml
├── extension_attributes.xml
├── frontend
│ ├── di.xml
│ └── events.xml
└── module.xml
├── phpstan.neon.dist
├── registration.php
└── view
├── adminhtml
├── requirejs-config.js
├── ui_component
│ └── product_attribute_add_form.xml
└── web
│ └── js
│ └── product-attributes.js
└── frontend
├── layout
└── smile_custom_entity_entity_view_product_list.xml
└── templates
└── entity
└── attribute
└── custom_entity
└── renderer.phtml
/Api/CustomEntityProductLinkManagementInterface.php:
--------------------------------------------------------------------------------
1 | initAttributes($productAttributeCollectionFactory);
34 | }
35 |
36 | /**
37 | * List of product attributes using custom entities as frontend input.
38 | *
39 | * @return ProductAttributeInterface[]
40 | */
41 | public function getCustomEntityProductAttributes(): array
42 | {
43 | return $this->customEntityProductAttributes;
44 | }
45 |
46 | /**
47 | * Init attribute list.
48 | *
49 | * @param ProductAttributeCollectionFactory $attributeCollectionFactory Product attribute collection factory.
50 | */
51 | private function initAttributes(ProductAttributeCollectionFactory $attributeCollectionFactory): void
52 | {
53 | $attributeCollection = $attributeCollectionFactory->create();
54 | $attributeCollection->addFieldToFilter(ProductAttributeInterface::FRONTEND_INPUT, 'smile_custom_entity');
55 |
56 | /** @var ProductAttributeInterface[] $attributeCollectionItems */
57 | $attributeCollectionItems = $attributeCollection->getItems();
58 | $this->customEntityProductAttributes = $attributeCollectionItems;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Helper/Product.php:
--------------------------------------------------------------------------------
1 | filterableAttributeList = $filterableAttributeList;
38 | }
39 |
40 | /**
41 | * Return custom entities for product and attribute code.
42 | *
43 | * @param ProductInterface $product Product.
44 | * @param string $attributeCode Attribuce code.
45 | * @return CustomEntityInterface[]
46 | */
47 | public function getCustomEntities(ProductInterface $product, string $attributeCode): array
48 | {
49 | $result = [];
50 | /** @var DataObject $extensionAttributes */
51 | $extensionAttributes = $product->getExtensionAttributes();
52 | $customEntities = $extensionAttributes->getCustomEntities();
53 | if ($customEntities) {
54 | foreach ($customEntities as $customEntity) {
55 | if ($customEntity->getProductAttributeCode() !== $attributeCode || !$customEntity->getIsActive()) {
56 | continue;
57 | }
58 | $result[] = $customEntity;
59 | }
60 | }
61 |
62 | return $result;
63 | }
64 |
65 | /**
66 | * Return filterable attribute code for a custom entity.
67 | *
68 | * @param CustomEntityInterface $customEntity Custom entity.
69 | */
70 | public function getFilterableAttributeCode(CustomEntityInterface $customEntity): ?string
71 | {
72 | if (!array_key_exists($customEntity->getId(), $this->filterableAttributeCodes)) {
73 | $this->filterableAttributeCodes[$customEntity->getId()] = '';
74 | foreach ($this->filterableAttributeList->getList() as $attribute) {
75 | if (
76 | $attribute->getFrontendInput() == 'smile_custom_entity' &&
77 | $attribute->getCustomEntityAttributeSetId() == $customEntity->getAttributeSetId()
78 | ) {
79 | $this->filterableAttributeCodes[$customEntity->getId()] = $attribute->getAttributeCode();
80 | break;
81 | }
82 | }
83 | }
84 |
85 | return $this->filterableAttributeCodes[$customEntity->getId()];
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Model/CustomEntityProductLinkManagement.php:
--------------------------------------------------------------------------------
1 | helper = $helper;
30 | $this->customEntityCollectionFactory = $customEntityCollectionFactory;
31 | }
32 |
33 | /**
34 | * Return custom entities assigned to a product.
35 | *
36 | * @return CustomEntityInterface[][]|null
37 | * @throws LocalizedException
38 | */
39 | public function getCustomEntities(Product $product): ?array
40 | {
41 | /** @var CustomEntityInterface[][] $entities */
42 | $entities = [];
43 | $entityIds = [];
44 |
45 | foreach ($this->helper->getCustomEntityProductAttributes() as $customEntityAttribute) {
46 | $customEntityAttributeCode = $customEntityAttribute->getAttributeCode();
47 | $productCustomEntityIds = $product->getData($customEntityAttributeCode);
48 | if ($productCustomEntityIds) {
49 | if (is_string($productCustomEntityIds)) {
50 | $productCustomEntityIds = explode(',', $productCustomEntityIds);
51 | }
52 | foreach ($productCustomEntityIds ?? [] as $entityId) {
53 | $entityIds[$entityId][] = $customEntityAttributeCode;
54 | }
55 | }
56 | }
57 |
58 | if (!empty($entityIds)) {
59 | $collection = $this->customEntityCollectionFactory->create()
60 | ->addAttributeToSelect('*')
61 | ->addAttributeToFilter('entity_id', ['in' => array_keys($entityIds)]);
62 |
63 | foreach ($collection->getItems() as $customEntity) {
64 | foreach ($entityIds[$customEntity->getId()] as $customEntityAttributeCode) {
65 | $entities[$customEntityAttributeCode][] = $customEntity;
66 | }
67 | }
68 | }
69 |
70 | return $entities;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Model/Entity/Attribute/Frontend/CustomEntity.php:
--------------------------------------------------------------------------------
1 | renderers = $renderers;
55 | $this->productHelper = $productHelper;
56 | }
57 |
58 | /**
59 | * Get value of object
60 | *
61 | * @param DataObject $object Object.
62 | * @throws NoSuchEntityException
63 | */
64 | public function getValue(DataObject $object): string
65 | {
66 | /** @var ProductInterface $object */
67 | $value = [];
68 | $customEntities = $this->productHelper->getCustomEntities($object, $this->getAttribute()->getAttributeCode());
69 | foreach ($customEntities as $entity) {
70 | $value[] = $this->getRenderer($entity)->toHtml();
71 | }
72 |
73 | return implode(' ', $value);
74 | }
75 |
76 | /**
77 | * Return custom entity renderer.
78 | *
79 | * @param CustomEntityInterface $customEntity Custom entity.
80 | * @throws NoSuchEntityException
81 | */
82 | private function getRenderer(CustomEntityInterface $customEntity): Renderer
83 | {
84 | /** @var \Smile\CustomEntity\Model\CustomEntity $customEntity */
85 | $renderer = $this->renderers[$customEntity->getAttributeSetUrlKey()] ?? $this->renderers['default'];
86 | $renderer->setCustomEntity($customEntity);
87 |
88 | return $renderer;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Model/Entity/Attribute/Source/CustomEntity.php:
--------------------------------------------------------------------------------
1 | customEntityCollectionFactory = $customEntityCollectionFactory;
20 | }
21 |
22 | /**
23 | * Get all options
24 | */
25 | public function getAllOptions(): array
26 | {
27 | $options = [];
28 | $collection = $this->customEntityCollectionFactory->create()
29 | ->addAttributeToSelect('name')
30 | ->addAttributeToFilter('attribute_set_id', $this->getAttribute()->getCustomEntityAttributeSetId());
31 |
32 | foreach ($collection->getItems() as $customEntity) {
33 | $options[] = [
34 | 'value' => $customEntity->getId(),
35 | 'label' => $customEntity->getName(),
36 | ];
37 | }
38 |
39 | return $options;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Model/Layer/CustomEntity.php:
--------------------------------------------------------------------------------
1 | productHelper = $productHelper;
48 | $this->registry = $registry;
49 | $this->queryFactory = $queryFactory;
50 | }
51 |
52 | /**
53 | * @inheritdoc
54 | */
55 | public function filter($collection, Category $category): void
56 | {
57 | parent::filter($collection, $category);
58 | $currentCustomEntity = $this->getCurrentCustomEntity();
59 | if (null !== $currentCustomEntity && $currentCustomEntity->getId() && $this->getAttributeCode()) {
60 | $query = $this->queryFactory->create(
61 | QueryInterface::TYPE_TERM,
62 | ['field' => $this->getAttributeCode(), 'value' => $currentCustomEntity->getId()]
63 | );
64 |
65 | /** @var Collection $collection */
66 | $collection->addQueryFilter($query);
67 | }
68 | }
69 |
70 | /**
71 | * Return current custom entity interface.
72 | */
73 | private function getCurrentCustomEntity(): ?CustomEntityInterface
74 | {
75 | return $this->registry->registry('current_custom_entity');
76 | }
77 |
78 | /**
79 | * Return attribute code link to current custom entity.
80 | */
81 | private function getAttributeCode(): ?string
82 | {
83 | return $this->productHelper->getFilterableAttributeCode($this->getCurrentCustomEntity());
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Model/Product/CustomEntity/ReadHandler.php:
--------------------------------------------------------------------------------
1 | customEntityLinkManager = $customEntityLinkManager;
27 | }
28 |
29 | /**
30 | * @inheritDoc
31 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
32 | */
33 | public function execute($entity, $arguments = [])
34 | {
35 | if ($entity->getId()) {
36 | $customEntitiesByCode = $this->customEntityLinkManager->getCustomEntities($entity);
37 | $attributeValues = [];
38 | $productCustomEntities = [];
39 |
40 | foreach ($customEntitiesByCode as $attributeCode => $customEntities) {
41 | /** @var DataObject $customEntity */
42 | foreach ($customEntities as $customEntity) {
43 | $customEntityClone = clone $customEntity;
44 | $customEntityClone->setProductAttributeCode($attributeCode);
45 | $attributeValues[$attributeCode][] = $customEntity->getId();
46 | $productCustomEntities[] = $customEntityClone;
47 | }
48 | }
49 |
50 | $entity->addData($attributeValues);
51 |
52 | $entityExtension = $entity->getExtensionAttributes();
53 | $entityExtension->setCustomEntities($productCustomEntities);
54 | $entity->setExtensionAttributes($entityExtension);
55 | }
56 |
57 | return $entity;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Observer/Adminhtml/AddFieldsToAttributeObserver.php:
--------------------------------------------------------------------------------
1 | moduleManager = $moduleManager;
37 | $this->attributeSetOptions = $attributeSetOptions;
38 | $this->registry = $registry;
39 | }
40 |
41 | /**
42 | * Append custom_entity_attribute_set_id field.
43 | *
44 | * @param Observer $observer Observer
45 | */
46 | public function execute(Observer $observer): void
47 | {
48 | if (!$this->moduleManager->isOutputEnabled('Smile_CustomEntityProductLink')) {
49 | return;
50 | }
51 |
52 | /** @var Form $form */
53 | $form = $observer->getForm();
54 | $fieldset = $form->getElement('base_fieldset');
55 | $fieldset->addField(
56 | 'custom_entity_attribute_set_id',
57 | 'select',
58 | [
59 | 'name' => 'custom_entity_attribute_set_id',
60 | 'label' => __('Custom entity type'),
61 | 'title' => __('Custom entity type'),
62 | 'values' => $this->attributeSetOptions->toOptionArray(),
63 | ]
64 | );
65 | if ($this->getAttributeObject() && $this->getAttributeObject()->getAttributeId()) {
66 | $form->getElement('custom_entity_attribute_set_id')->setDisabled(1);
67 | }
68 | }
69 |
70 | /**
71 | * Get attribute object.
72 | */
73 | private function getAttributeObject(): ?AttributeInterface
74 | {
75 | return $this->registry->registry('entity_attribute');
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Observer/Adminhtml/CatalogProductFormExcludedAttributesObserver.php:
--------------------------------------------------------------------------------
1 | attributeAction = $attributeAction;
28 | }
29 |
30 | /**
31 | * Excluded smile custom entity attributes.
32 | *
33 | * @param Observer $observer Observer.
34 | */
35 | public function execute(Observer $observer): void
36 | {
37 | /** @var Attributes $attributesTab */
38 | $attributesTab = $observer->getEvent()->getData('object');
39 | $attributesTab->setFormExcludedFieldList(
40 | array_merge($attributesTab->getFormExcludedFieldList(), $this->getCustomEntityAttributeCodes())
41 | );
42 | }
43 |
44 | /**
45 | * Return attribute codes.
46 | *
47 | * @return array
48 | */
49 | private function getCustomEntityAttributeCodes(): array
50 | {
51 | return array_map(function ($attribute) {
52 | return $attribute->getAttributeCode();
53 | }, $this->getCustomEntityAttributes());
54 | }
55 |
56 | /**
57 | * Return custom entity attributes.
58 | *
59 | * @return array|DataObject[]
60 | */
61 | private function getCustomEntityAttributes(): array
62 | {
63 | $attributes = $this->attributeAction->getAttributes()->getItems();
64 |
65 | return array_filter($attributes, function ($attribute) {
66 | return $attribute->getFrontendInput() == 'smile_custom_entity';
67 | });
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Observer/Adminhtml/CustomEntityAttributeSaveBeforeObserver.php:
--------------------------------------------------------------------------------
1 | getEvent()->getData('attribute');
27 |
28 | if ($attribute->getFrontendInput() == 'smile_custom_entity') {
29 | $attribute->setBackendType('text');
30 | $attribute->setFrontendModel(CustomEntity::class);
31 | $attribute->setBackendModel(ArrayBackend::class);
32 | $attribute->setSourceModel(Source::class);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Observer/Catalog/Product/AddCustomEntitiesInformation.php:
--------------------------------------------------------------------------------
1 | catalogConfig = $catalogConfig;
27 | $this->customEntityCollectionFactory = $customEntityCollectionFactory;
28 | }
29 |
30 | /**
31 | * Add custom entities information on product collection.
32 | *
33 | * @SuppressWarnings(PHPMD.CyclomaticComplexity)
34 | */
35 | public function execute(Observer $observer): void
36 | {
37 | /** @var Collection $collection */
38 | $collection = $observer->getEvent()->getData('collection');
39 | $attributeCodes = $this->getCustomEntityProductAttribute();
40 | $productCustomEntityList = [];
41 | $customEntityIds = [];
42 |
43 | if (empty($attributeCodes)) {
44 | return;
45 | }
46 |
47 | foreach ($collection as $product) {
48 | foreach ($attributeCodes as $attributeCode) {
49 | if (!$product->hasData($attributeCode)) {
50 | continue;
51 | }
52 | $productCustomEntityIds = $product->getData($attributeCode);
53 | if (is_string($productCustomEntityIds)) {
54 | $productCustomEntityIds = explode(',', $productCustomEntityIds);
55 | }
56 | foreach ($productCustomEntityIds ?? [] as $productCustomEntityId) {
57 | $productCustomEntityList[$product->getId()][$attributeCode][] = $productCustomEntityId;
58 | $customEntityIds[$productCustomEntityId] = $productCustomEntityId;
59 | }
60 | }
61 | }
62 |
63 | if (empty($customEntityIds)) {
64 | return;
65 | }
66 |
67 | $customEntityList = $this->getCustomEntityByIds($customEntityIds);
68 |
69 | foreach (array_keys($productCustomEntityList) as $productId) {
70 | $product = $collection->getItemById($productId);
71 | $productCustomEntities = [];
72 | foreach ($productCustomEntityList[$product->getId()] as $attributeCode => $customEntityIds) {
73 | foreach ($customEntityIds as $customEntityId) {
74 | $customEntity = $customEntityList[$customEntityId];
75 | $customEntity->setProductAttributeCode($attributeCode);
76 | $productCustomEntities[] = $customEntity;
77 | }
78 | }
79 | $attributeValues = $productCustomEntityList[$product->getId()];
80 | $product->addData($attributeValues);
81 |
82 | $entityExtension = $product->getExtensionAttributes();
83 | $entityExtension->setCustomEntities($productCustomEntities);
84 | $product->setExtensionAttributes($entityExtension);
85 | }
86 | }
87 |
88 | /**
89 | * Return an array of custom entity matching the given ids, index by the ids.
90 | */
91 | private function getCustomEntityByIds(array $customEntityIds): array
92 | {
93 | $customEntityList = [];
94 | $customEntityCollection = $this->customEntityCollectionFactory->create()
95 | ->addAttributeToSelect('*')
96 | ->addAttributeToFilter('entity_id', ['in' => $customEntityIds]);
97 |
98 | foreach ($customEntityCollection->getItems() as $customEntity) {
99 | $customEntityList[$customEntity->getId()] = $customEntity;
100 | }
101 |
102 | return $customEntityList;
103 | }
104 |
105 | /**
106 | * Get custom entity product attribute code.
107 | *
108 | * @return string[]
109 | */
110 | private function getCustomEntityProductAttribute(): array
111 | {
112 | return array_keys(array_filter(
113 | $this->catalogConfig->getAttributesUsedInProductListing(),
114 | function (AbstractAttribute $attribute) {
115 | return $attribute->getFrontendInput() == 'smile_custom_entity';
116 | }
117 | ));
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Observer/Catalog/Product/AddCustomEntityAttributeTypeObserver.php:
--------------------------------------------------------------------------------
1 | moduleManager = $moduleManager;
27 | }
28 |
29 | /**
30 | * Add custom entities attribute type observer.
31 | *
32 | * @param Observer $observer Observer.
33 | */
34 | public function execute(Observer $observer): void
35 | {
36 | if (!$this->moduleManager->isOutputEnabled('Smile_CustomEntityProductLink')) {
37 | return;
38 | }
39 |
40 | /** @var DataObject $response */
41 | $response = $observer->getEvent()->getResponse();
42 | $types = $response->getTypes();
43 |
44 | $types[] = [
45 | 'value' => 'smile_custom_entity',
46 | 'label' => __('Custom Entity'),
47 | 'hide_fields' => [
48 | 'is_unique',
49 | 'is_required',
50 | 'frontend_class',
51 | '_default_value',
52 | ],
53 | ];
54 |
55 | $response->setTypes($types);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Plugin/Block/Product/ListProductPlugin.php:
--------------------------------------------------------------------------------
1 | getLoadedProductCollection() as $product) {
26 | // @phpstan-ignore-next-line
27 | $customEntities = $product->getExtensionAttributes()->getCustomEntities();
28 | $identities = [];
29 | if ($customEntities) {
30 | foreach ($customEntities as $customEntity) {
31 | // @codingStandardsIgnoreLine
32 | $identities = array_merge($identities, $customEntity->getIdentities());
33 | }
34 | }
35 | }
36 |
37 | return $identities;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Plugin/Block/Product/ViewPlugin.php:
--------------------------------------------------------------------------------
1 | getProduct();
27 | if (!$product) {
28 | return $identities;
29 | }
30 |
31 | // @todo Optimization: only custom entities if is visible on front
32 | // @phpstan-ignore-next-line
33 | $customEntities = $product->getExtensionAttributes()->getCustomEntities();
34 | if ($customEntities) {
35 | /** @var CustomEntityInterface $customEntity */
36 | foreach ($customEntities as $customEntity) {
37 | // @codingStandardsIgnoreLine
38 | $identities = array_merge($identities, $customEntity->getIdentities());
39 | }
40 | }
41 |
42 | return $identities;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Plugin/Catalog/Controller/Adminhtml/Product/Initialization/HelperPlugin.php:
--------------------------------------------------------------------------------
1 | helper = $helper;
26 | }
27 |
28 | /**
29 | * Clean custom entity input of the product edit form.
30 | *
31 | * @param Helper $helper Original helper.
32 | * @param Product $product Product.
33 | * @param array $productData Post product data.
34 | * @return array|null
35 | * @SuppressWarnings(PHPMD.UnusedFormalParameters)
36 | */
37 | // @codingStandardsIgnoreLine
38 | public function beforeInitializeFromData(
39 | Helper $helper,
40 | Product $product,
41 | array $productData
42 | ): ?array {
43 | foreach ($this->helper->getCustomEntityProductAttributes() as $attribute) {
44 | if (!isset($productData[$attribute->getAttributeCode()])) {
45 | $productData[$attribute->getAttributeCode()] = [];
46 | }
47 | }
48 |
49 | return [$product, $productData];
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Plugin/Catalog/Ui/DataProvider/Product/Form/Modifier/EavPlugin.php:
--------------------------------------------------------------------------------
1 | arrayManager = $arrayManager;
27 | }
28 |
29 | /**
30 | * Fix custom entity field meta.
31 | *
32 | * @param EavModifier $subject Object.
33 | * @param callable $proceed Original method.
34 | * @param ProductAttributeInterface $attribute Attribute.
35 | * @param string $groupCode Group code.
36 | * @param int $sortOrder Sort order.
37 | * @return array|null
38 | */
39 | public function aroundSetupAttributeMeta(
40 | EavModifier $subject,
41 | callable $proceed,
42 | ProductAttributeInterface $attribute,
43 | string $groupCode,
44 | int $sortOrder
45 | ): ?array {
46 | $meta = $proceed($attribute, $groupCode, $sortOrder);
47 |
48 | if ($attribute->getFrontendInput() == "smile_custom_entity") {
49 | $configPath = ltrim($subject::META_CONFIG_PATH, ArrayManager::DEFAULT_PATH_DELIMITER);
50 |
51 | /** @var Attribute $attribute */
52 | $fieldConfig = [
53 | 'component' => 'Magento_Ui/js/form/element/ui-select',
54 | 'formElement' => 'multiselect',
55 | 'elementTmpl' => 'ui/grid/filters/elements/ui-select',
56 | 'filterOptions' => true,
57 | 'multiple' => true,
58 | 'options' => $attribute->getSource()->getAllOptions(),
59 | 'disableLabel' => true,
60 | 'required' => false,
61 | ];
62 |
63 | $meta = $this->arrayManager->merge($configPath, $meta, $fieldConfig);
64 | }
65 |
66 | return $meta;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Plugin/Controller/Entity/ViewPlugin.php:
--------------------------------------------------------------------------------
1 | layerResolver = $layerResolver;
36 | $this->registry = $registry;
37 | $this->productHelper = $productHelper;
38 | }
39 |
40 | /**
41 | * Create an layer context and add body class.
42 | *
43 | * @param View $subject Custom entity view controller.
44 | * @param callable $proceed Callable method.
45 | * @return mixed
46 | * @SuppressWarnings(PHPMD.UnusedFormalParameters)
47 | */
48 | public function aroundExecute(View $subject, callable $proceed)
49 | {
50 | $this->layerResolver->create('smile_custom_entity');
51 | /** @var \Magento\Framework\View\Result\Page $page */
52 | $page = $proceed();
53 | if ($this->hasFilterableAttribute()) {
54 | $page->addPageLayoutHandles(['product' => 'list']);
55 | $page->getConfig()->addBodyClass('page-products');
56 | }
57 |
58 | return $page;
59 | }
60 |
61 | /**
62 | * Return true if has filterable attribute for current custom entity.
63 | */
64 | private function hasFilterableAttribute(): bool
65 | {
66 | return $this->productHelper->getFilterableAttributeCode(
67 | $this->registry->registry('current_custom_entity')
68 | ) !== '';
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Plugin/Model/Condition/Product/AbstractProduct.php:
--------------------------------------------------------------------------------
1 | getAttributeObject())) {
20 | if ($subject->getAttributeObject()->getFrontendInput() == 'smile_custom_entity') {
21 | $result = 'multiselect';
22 | }
23 | }
24 |
25 | return $result;
26 | }
27 |
28 | /**
29 | * Return multiselect for smile_custom_entity attribute
30 | */
31 | public function afterGetValueElementType(Subject $subject, string $result): string
32 | {
33 | if (is_object($subject->getAttributeObject())) {
34 | if ($subject->getAttributeObject()->getFrontendInput() == 'smile_custom_entity') {
35 | $result = 'multiselect';
36 | }
37 | }
38 |
39 | return $result;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Plugin/Model/ProductPlugin.php:
--------------------------------------------------------------------------------
1 | helper = $helper;
26 | }
27 |
28 | /**
29 | * Append custom entity identities when update product attributes.
30 | *
31 | * @param Product $source Product model.
32 | * @param array $identities Identities.
33 | * @return array|null
34 | */
35 | public function afterGetIdentities(Product $source, array $identities): ?array
36 | {
37 | $customEntityProductAttributes = $this->helper->getCustomEntityProductAttributes();
38 |
39 | foreach ($customEntityProductAttributes as $customEntityProductAttribute) {
40 | $attributeCode = $customEntityProductAttribute->getAttributeCode();
41 | if (!$source->hasData($attributeCode) || !$source->dataHasChangedFor($attributeCode)) {
42 | continue;
43 | }
44 | $customEntityIds = $source->getData($attributeCode);
45 | if (is_string($customEntityIds)) {
46 | $customEntityIds = explode(',', $customEntityIds);
47 | }
48 | foreach ($customEntityIds ?? [] as $customEntityId) {
49 | $identities[] = CustomEntity::CACHE_TAG . '_' . $customEntityId;
50 | }
51 | }
52 |
53 | return array_unique($identities);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Plugin/Model/ResourceModel/Eav/Attribute.php:
--------------------------------------------------------------------------------
1 | getFrontendInput() === 'smile_custom_entity') {
17 | $result = (bool) $subject->getIsVisible();
18 | }
19 |
20 | return $result;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Setup/Patch/Data/MoveCustomEntityLink.php:
--------------------------------------------------------------------------------
1 | resourceConnection = $resourceConnection;
25 | }
26 |
27 | /**
28 | * @inheritDoc
29 | */
30 | public function apply()
31 | {
32 | $connection = $this->resourceConnection->getConnection();
33 | $customEntityLink = $connection->getTableName('catalog_product_custom_entity_link');
34 |
35 | if ($connection->isTableExists($customEntityLink)) {
36 | $catalogText = $connection->getTableName('catalog_product_entity_text');
37 | $fields = ['attribute_id', 'value', 'entity_id'];
38 |
39 | $select = $connection->select()
40 | ->from(
41 | $customEntityLink,
42 | [
43 | 'attribute_id' => 'attribute_id',
44 | 'value' => 'GROUP_CONCAT(custom_entity_id SEPARATOR ",")',
45 | 'entity_id' => 'product_id',
46 | ]
47 | )
48 | ->group(['product_id', 'attribute_id']);
49 |
50 | if ($connection->tableColumnExists($catalogText, 'row_id')) {
51 | $fields = ['attribute_id', 'value', 'row_id'];
52 | $select = $connection->select()
53 | ->from(
54 | ['link' => $customEntityLink],
55 | [
56 | 'attribute_id' => 'link.attribute_id',
57 | 'value' => 'GROUP_CONCAT(link.custom_entity_id SEPARATOR ",")',
58 | ]
59 | )
60 | ->join(
61 | ['product' => $connection->getTableName('catalog_product_entity')],
62 | 'product.entity_id = link.product_id',
63 | ['row_id' => 'product.row_id']
64 | )
65 | ->group(['link.product_id', 'link.attribute_id']);
66 | }
67 |
68 | $connection->query(
69 | $connection->insertFromSelect($select, $catalogText, $fields, AdapterInterface::INSERT_IGNORE)
70 | );
71 | }
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * @inheritDoc
78 | */
79 | public static function getDependencies()
80 | {
81 | return [];
82 | }
83 |
84 | /**
85 | * @inheritDoc
86 | */
87 | public function getAliases()
88 | {
89 | return [];
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Setup/Patch/Data/UpdateProductCustomEntityAttribute.php:
--------------------------------------------------------------------------------
1 | resourceConnection = $resourceConnection;
29 | }
30 |
31 | /**
32 | * @inheritDoc
33 | */
34 | public function apply()
35 | {
36 | $connection = $this->resourceConnection->getConnection();
37 |
38 | $connection->update(
39 | $connection->getTableName('eav_attribute'),
40 | ['backend_model' => ArrayBackend::class, 'backend_type' => 'text', 'source_model' => Source::class],
41 | ['frontend_input = ?' => 'smile_custom_entity']
42 | );
43 |
44 | return $this;
45 | }
46 |
47 | /**
48 | * @inheritDoc
49 | */
50 | public static function getDependencies()
51 | {
52 | return [];
53 | }
54 |
55 | /**
56 | * @inheritDoc
57 | */
58 | public function getAliases()
59 | {
60 | return [];
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "smile/module-custom-entity-product-link",
3 | "type": "magento2-module",
4 | "description": "Smile - Custom Entity Product link Module",
5 | "keywords": ["magento2", "custom", "entity", "link", "product"],
6 | "authors": [
7 | {
8 | "name": "Aurélien FOUCRET",
9 | "email": "aurelien.foucret@smile.fr"
10 | },
11 | {
12 | "name": "Maxime LECLERCQ",
13 | "email": "maxime.leclercq@smile.fr"
14 | },
15 | {
16 | "name": "Cédric MAGREZ",
17 | "email": "cedric.magrez@smile.fr"
18 | }
19 | ],
20 | "license": "OSL-3.0",
21 | "config": {
22 | "allow-plugins": {
23 | "dealerdirect/phpcodesniffer-composer-installer": true,
24 | "magento/composer-dependency-version-audit-plugin": true,
25 | "magento/magento-composer-installer": false,
26 | "php-http/discovery": true
27 | },
28 | "sort-packages": true
29 | },
30 | "require": {
31 | "php": "^7.4 || ^8.1",
32 | "smile/module-custom-entity": "^1.3",
33 | "smile/elasticsuite": "^2.8.0"
34 | },
35 | "require-dev": {
36 | "smile/magento2-smilelab-quality-suite": "^3.0"
37 | },
38 | "autoload": {
39 | "files": [
40 | "registration.php"
41 | ],
42 | "psr-4": {
43 | "Smile\\CustomEntityProductLink\\" : ""
44 | }
45 | },
46 | "repositories": [
47 | {
48 | "type": "composer",
49 | "url": "https://repo.magento.com/"
50 | }
51 | ],
52 | "minimum-stability": "dev",
53 | "prefer-stable": true
54 | }
55 |
--------------------------------------------------------------------------------
/etc/adminhtml/di.xml:
--------------------------------------------------------------------------------
1 |
2 |