├── .gitignore ├── Api ├── ConfigInterface.php ├── Data │ ├── RuleInterface.php │ └── RuleSearchResultsInterface.php ├── PositionsInterface.php ├── RelatedCollectionInterface.php ├── RelatedItemsProcessorInterface.php ├── RelatedResourceModelInterface.php └── RuleRepositoryInterface.php ├── Block ├── Adminhtml │ ├── CheckEnableInfo.php │ ├── Rule │ │ ├── DuplicateButton.php │ │ └── Edit │ │ │ ├── WhatConditions.php │ │ │ ├── WhereConditions.php │ │ │ └── WhereConditions │ │ │ └── Where.php │ ├── System │ │ └── Config │ │ │ └── Form │ │ │ └── Info.php │ └── TmpInfo.php └── RelatedProductList.php ├── Console └── Command │ └── ApplyRules.php ├── Controller └── Adminhtml │ ├── Actions.php │ ├── Promo │ └── NewConditionHtml.php │ ├── Rule.php │ └── Rule │ ├── Apply.php │ ├── Delete.php │ ├── Edit.php │ ├── Index.php │ ├── InlineEdit.php │ ├── MassDelete.php │ ├── MassStatus.php │ ├── NewAction.php │ └── Save.php ├── Cron └── UpdateRelatedProducts.php ├── LICENSE.txt ├── Model ├── ActionValidator.php ├── AutoRelatedProductAction.php ├── Condition │ └── Product.php ├── Config.php ├── Config │ └── Source │ │ ├── MergeType.php │ │ ├── Positions.php │ │ ├── RelatedTemplate.php │ │ └── SortBy.php ├── RelatedItemsProcessor.php ├── ResourceModel │ ├── Rule.php │ └── Rule │ │ └── Collection.php ├── Rule.php ├── RuleManager.php └── RuleRepository.php ├── Observer └── Frontend │ └── Layout │ └── GenerateBlocksAfter.php ├── Plugin ├── Frontend │ └── Magento │ │ ├── Catalog │ │ ├── Block │ │ │ └── Product │ │ │ │ ├── ProductList │ │ │ │ ├── Related.php │ │ │ │ └── Upsell.php │ │ │ │ └── View │ │ │ │ └── Details.php │ │ └── Model │ │ │ └── ResourceModel │ │ │ └── Product │ │ │ └── Link │ │ │ └── Product │ │ │ └── Collection.php │ │ ├── Framework │ │ └── View │ │ │ └── Result │ │ │ └── Layout.php │ │ └── TargetRule │ │ └── Block │ │ └── Catalog │ │ └── Product │ │ └── ProductList │ │ └── Related.php └── Magento │ └── SalesRule │ └── Model │ └── Rule │ └── Condition │ ├── Combine.php │ └── Product │ └── Combine.php ├── README.md ├── Ui ├── Component │ └── Listing │ │ └── Column │ │ └── Actions.php └── DataProvider │ └── Rule │ ├── Form │ └── RuleDataProvider.php │ └── Rule.php ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ ├── menu.xml │ ├── routes.xml │ └── system.xml ├── config.xml ├── crontab.xml ├── db_schema.xml ├── di.xml ├── frontend │ ├── di.xml │ └── events.xml └── module.xml ├── registration.php └── view ├── adminhtml ├── layout │ ├── autorp_info.xml │ ├── autorp_rule_edit.xml │ ├── autorp_rule_index.xml │ ├── default.xml │ └── related_product_list.xml ├── templates │ ├── form │ │ └── versionsManager.phtml │ ├── info.phtml │ └── tmpInfo.phtml ├── ui_component │ ├── autorp_listing.xml │ └── autorp_rule_form.xml └── web │ └── js │ ├── form │ └── element │ │ └── is-product-conditions.js │ └── lib │ └── core │ └── collection.js └── frontend └── templates └── product └── list └── related_product_items.phtml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /Api/ConfigInterface.php: -------------------------------------------------------------------------------- 1 | config = $config; 34 | parent::__construct($context, $data); 35 | } 36 | 37 | /** 38 | * @return bool 39 | */ 40 | public function isEnabled(): bool 41 | { 42 | return $this->config->isEnabled(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Block/Adminhtml/Rule/DuplicateButton.php: -------------------------------------------------------------------------------- 1 | authorization->isAllowed("Magefan_AutoRelatedProduct::rule")) { 24 | return $data; 25 | } 26 | 27 | if ($this->getObjectId()) { 28 | $data = [ 29 | 'label' => __('Duplicate (Plus)'), 30 | 'class' => 'duplicate', 31 | 'on_click' => '(typeof versionsManager !== "undefined" && versionsManager._currentPlan == "Basic") ? versionsManager.showAlert("Plus or Extra") : window.location=\'' . $this->getDuplicateUrl() . '\'', 32 | 'sort_order' => 40, 33 | ]; 34 | } 35 | return $data; 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | public function getDuplicateUrl() 42 | { 43 | return $this->getUrl('mfautorp/*/duplicate', ['id' => $this->getObjectId()]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Block/Adminhtml/Rule/Edit/WhatConditions.php: -------------------------------------------------------------------------------- 1 | rendererFieldset = $rendererFieldset; 61 | $this->actions = $actions; 62 | $this->ruleFactory = $ruleFactory; 63 | parent::__construct($context, $registry, $formFactory, $data); 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | * @codeCoverageIgnore 69 | */ 70 | public function getTabClass() 71 | { 72 | return null; 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function getTabUrl() 79 | { 80 | return null; 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | public function isAjaxLoaded() 87 | { 88 | return false; 89 | } 90 | 91 | /** 92 | * {@inheritdoc} 93 | */ 94 | public function getTabLabel() 95 | { 96 | return __('What Conditions'); 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function getTabTitle() 103 | { 104 | return __('What Conditions'); 105 | } 106 | 107 | /** 108 | * {@inheritdoc} 109 | */ 110 | public function canShowTab() 111 | { 112 | return true; 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | */ 118 | public function isHidden() 119 | { 120 | return false; 121 | } 122 | 123 | /** 124 | * @return \Magento\Backend\Block\Widget\Form\Generic 125 | * @throws \Magento\Framework\Exception\LocalizedException 126 | */ 127 | protected function _prepareForm() 128 | { 129 | $model = $this->_coreRegistry->registry('current_model'); 130 | $form = $this->addTabToForm($model); 131 | $this->setForm($form); 132 | return parent::_prepareForm(); 133 | } 134 | 135 | /** 136 | * @param $model 137 | * @param $fieldsetId 138 | * @param $formName 139 | * @return \Magento\Framework\Data\Form 140 | * @throws \Magento\Framework\Exception\LocalizedException 141 | */ 142 | protected function addTabToForm($model, $fieldsetId = 'conditions_fieldset', $formName = 'autorp_rule_form') 143 | { 144 | if (!$this->getRequest()->getParam('js_form_object')) { 145 | $this->getRequest()->setParam('js_form_object', $formName); 146 | } 147 | 148 | $rule = $this->ruleFactory->create(); 149 | $rule->setData('conditions_serialized', $model->getData('conditions_serialized')); 150 | $model = $rule; 151 | $fieldSetId = $model->getConditionsFieldSetId($formName); 152 | $newChildUrl = $this->getUrl( 153 | 'sales_rule/promo_quote/newConditionHtml/form/' . $fieldSetId, 154 | [ 155 | 'form_namespace' => $formName, 156 | ] 157 | ); 158 | /** @var \Magento\Framework\Data\Form $form */ 159 | $form = $this->_formFactory->create(); 160 | $form->setHtmlIdPrefix('rule_'); 161 | $renderer = $this->getLayout()->createBlock(Fieldset::class); 162 | $renderer = $renderer->setNameInLayout( 163 | 'mfautorp_what_conditions_serialized' 164 | )->setTemplate( 165 | 'Magento_CatalogRule::promo/fieldset.phtml' 166 | )->setNewChildUrl( 167 | $newChildUrl 168 | )->setFieldSetId( 169 | $fieldSetId 170 | ); 171 | $fieldset = $form->addFieldset( 172 | $fieldsetId, 173 | [ 174 | 'legend' => __( 175 | 'Apply the rule only to items matching the following conditions (leave blank for all items).' 176 | ) 177 | ] 178 | )->setRenderer( 179 | $renderer 180 | ); 181 | 182 | $fieldset->addField( 183 | 'conditions', 184 | 'text', 185 | [ 186 | 'name' => 'conditions', 187 | 'label' => __('conditions'), 188 | 'title' => __('conditions'), 189 | 'required' => true, 190 | 'data-form-part' => $formName 191 | ] 192 | )->setRule( 193 | $model 194 | )->setRenderer( 195 | $this->actions 196 | ); 197 | $form->setValues($model->getData()); 198 | $this->setConditionFormName($model->getConditions(), $formName, $fieldSetId); 199 | 200 | return $form; 201 | } 202 | 203 | /** 204 | * @param \Magento\Rule\Model\Condition\AbstractCondition $conditions 205 | * @param $formName 206 | * @param $jsFormName 207 | * @return void 208 | */ 209 | private function setConditionFormName(\Magento\Rule\Model\Condition\AbstractCondition $conditions, $formName, $jsFormName) 210 | { 211 | $conditions->setFormName($formName); 212 | $conditions->setJsFormObject($jsFormName); 213 | if ($conditions->getConditions() && is_array($conditions->getConditions())) { 214 | foreach ($conditions->getConditions() as $condition) { 215 | $this->setConditionFormName($condition, $formName, $jsFormName); 216 | } 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /Block/Adminhtml/Rule/Edit/WhereConditions.php: -------------------------------------------------------------------------------- 1 | rendererFieldset = $rendererFieldset; 56 | $this->conditions = $conditions; 57 | $this->ruleFactory = $ruleFactory; 58 | parent::__construct($context, $registry, $formFactory, $data); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | * @codeCoverageIgnore 64 | */ 65 | public function getTabClass() 66 | { 67 | return null; 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | public function getTabUrl() 74 | { 75 | return null; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function isAjaxLoaded() 82 | { 83 | return false; 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function getTabLabel() 90 | { 91 | return __('Where Conditions'); 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function getTabTitle() 98 | { 99 | return __('Where Conditions'); 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | public function canShowTab() 106 | { 107 | return true; 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | public function isHidden() 114 | { 115 | return false; 116 | } 117 | 118 | /** 119 | * @return \Magento\Backend\Block\Widget\Form\Generic 120 | * @throws \Magento\Framework\Exception\LocalizedException 121 | */ 122 | protected function _prepareForm() 123 | { 124 | $model = $this->_coreRegistry->registry('current_model'); 125 | $form = $this->addTabToForm($model); 126 | $this->setForm($form); 127 | return parent::_prepareForm(); 128 | } 129 | 130 | /** 131 | * @param $model 132 | * @param $fieldsetId 133 | * @param $formName 134 | * @return \Magento\Framework\Data\Form 135 | * @throws \Magento\Framework\Exception\LocalizedException 136 | */ 137 | protected function addTabToForm($model, $fieldsetId = 'actions_fieldset', $formName = 'autorp_rule_form') 138 | { 139 | if (!$this->getRequest()->getParam('js_form_object')) { 140 | $this->getRequest()->setParam('js_form_object', $formName); 141 | } 142 | 143 | $model = $this->_coreRegistry->registry('current_model'); 144 | $rule = $this->ruleFactory->create(); 145 | $rule->setData('conditions_serialized', $model->getData('actions_serialized')); 146 | $model = $rule; 147 | $fieldSetId = 'autorp_rule_formrule_actions_fieldset_' . $model->getId(); 148 | $newChildUrl = $this->getUrl( 149 | 'sales_rule/promo_quote/newActionHtml/form/' . $fieldSetId, 150 | ['form_namespace' => $formName] 151 | ); 152 | /** @var \Magento\Framework\Data\Form $form */ 153 | $form = $this->_formFactory->create(); 154 | $form->setHtmlIdPrefix('rule_where'); 155 | $renderer = $this->getLayout()->createBlock(Fieldset::class); 156 | $renderer = $renderer->setNameInLayout( 157 | 'mfautorp_where_actions_serialized' 158 | )->setTemplate( 159 | 'Magento_CatalogRule::promo/fieldset.phtml' 160 | )->setNewChildUrl( 161 | $newChildUrl 162 | )->setFieldSetId( 163 | $fieldSetId 164 | ); 165 | $fieldset = $form->addFieldset( 166 | $fieldsetId, 167 | [ 168 | 'legend' => __( 169 | 'Apply the rule only if the following conditions are met (leave blank for all products).' 170 | ) 171 | ] 172 | )->setRenderer( 173 | $renderer 174 | ); 175 | $fieldset->addField( 176 | 'actions', 177 | 'text', 178 | [ 179 | 'name' => 'actions', 180 | 'label' => __('actions'), 181 | 'title' => __('actions'), 182 | 'required' => true, 183 | 'data-form-part' => $formName 184 | ] 185 | )->setRule( 186 | $model 187 | )->setRenderer( 188 | $this->conditions 189 | ); 190 | $form->setValues($model->getData()); 191 | $this->setConditionFormName($model->getConditions(), $formName); 192 | return $form; 193 | } 194 | 195 | /** 196 | * Handles addition of form name to condition and its conditions. 197 | * 198 | * @param \Magento\Rule\Model\Condition\AbstractCondition $conditions 199 | * @param string $formName 200 | * @return void 201 | */ 202 | private function setConditionFormName(\Magento\Rule\Model\Condition\AbstractCondition $conditions, $formName) 203 | { 204 | $conditions->setFormName($formName); 205 | if ($conditions->getConditions() && is_array($conditions->getConditions())) { 206 | foreach ($conditions->getConditions() as $condition) { 207 | $this->setConditionFormName($condition, $formName); 208 | } 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /Block/Adminhtml/Rule/Edit/WhereConditions/Where.php: -------------------------------------------------------------------------------- 1 | getRule() && $element->getRule()->getConditions()) { 21 | $html = str_replace('conditions', 'actions', $element->getRule()->getConditions()->asHtmlRecursive()); 22 | } 23 | 24 | return $html; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/Form/Info.php: -------------------------------------------------------------------------------- 1 | config = $config; 50 | $this->getModuleVersion = $getModuleVersion; 51 | $this->ruleCollection = $ruleCollection; 52 | $this->session = $session; 53 | parent::__construct($context, $data); 54 | } 55 | 56 | /** 57 | * @return bool 58 | */ 59 | public function isSomeFeaturesRestricted(): bool 60 | { 61 | if ($this->getModuleVersion->execute('Magefan_AutoRelatedProductExtra') || $this->getModuleVersion->execute('Magefan_AutoRelatedProductPlus')) { 62 | return true; 63 | } 64 | 65 | return false; 66 | } 67 | 68 | /** 69 | * @return string 70 | */ 71 | public function getAffectedRulesByDisplayModes(): string 72 | { 73 | $rules = $this->ruleCollection->create() 74 | ->addFieldToFilter('status', 1); 75 | 76 | $connection = $rules->getConnection(); 77 | $tableName = $rules->getMainTable(); 78 | 79 | $conditions = []; 80 | 81 | if ($connection->tableColumnExists($tableName, 'from_one_category_only')) { 82 | $conditions[] = 'from_one_category_only = 1'; 83 | } 84 | 85 | if ($connection->tableColumnExists($tableName, 'only_with_higher_price')) { 86 | $conditions[] = 'only_with_higher_price = 1'; 87 | } 88 | 89 | if ($connection->tableColumnExists($tableName, 'only_with_lower_price')) { 90 | $conditions[] = 'only_with_lower_price = 1'; 91 | } 92 | 93 | if (!empty($conditions)) { 94 | $rules->getSelect()->where(implode(' OR ', $conditions)); 95 | } 96 | 97 | return implode(',', $rules->getAllIds()); 98 | } 99 | 100 | /** 101 | * @return string 102 | */ 103 | public function getAffectedRulesBySortBy(): string 104 | { 105 | $restrictedSortByOptionsIds = [ 106 | SortBy::NAME, 107 | SortBy::NEWEST, 108 | SortBy::PRICE_DESC, 109 | SortBy::PRICE_ASC 110 | ]; 111 | 112 | $rules = $this->ruleCollection->create() 113 | ->addFieldToFilter('status', 1) 114 | ->addFieldToFilter('sort_by', ['in' => $restrictedSortByOptionsIds]); 115 | 116 | return implode(',', $rules->getAllIds()); 117 | } 118 | 119 | /** 120 | * @return string 121 | */ 122 | public function toHtml() 123 | { 124 | if (!$this->config->isEnabled() || $this->session->getIsNeedToShowAlert() === false) { 125 | return ''; 126 | } 127 | 128 | $this->session->setIsNeedToShowAlert( 129 | !$this->isSomeFeaturesRestricted() && ($this->getAffectedRulesByDisplayModes() || $this->getAffectedRulesBySortBy()) 130 | ); 131 | 132 | return parent::_toHtml(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Block/RelatedProductList.php: -------------------------------------------------------------------------------- 1 | config = $config; 50 | $this->ruleManager = $ruleManager; 51 | parent::__construct($context, $data); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function getTemplate() 58 | { 59 | $theme = $this->_design->getDesignTheme(); 60 | while ($theme) { 61 | if ('Hyva/default' == $theme->getCode()) { 62 | return $this->_hTemplate; 63 | } 64 | $theme = $theme->getParentTheme(); 65 | } 66 | 67 | return $this->getPassedTemplate() ?: $this->getTemplateFromRule() ?: parent::getTemplate(); 68 | } 69 | 70 | /** 71 | * @return string 72 | */ 73 | private function getPassedTemplate(): string 74 | { 75 | return $this->_data['template'] ?? ''; 76 | } 77 | 78 | /** 79 | * @return string 80 | * @throws \Magento\Framework\Exception\LocalizedException 81 | */ 82 | private function getTemplateFromRule(): string 83 | { 84 | $rule = $this->getRule(); 85 | return $rule ? (string)$rule->getTemplate() : ''; 86 | } 87 | 88 | /** 89 | * @return string 90 | */ 91 | public function getTitle(): string 92 | { 93 | return (string)$this->getBlockModelData('title', 'getBlockTitle'); 94 | } 95 | 96 | /** 97 | * @return int 98 | */ 99 | public function getNumberOfProducts(): int 100 | { 101 | return (int)$this->getBlockModelData('number_of_products', 'getNumberOfProducts'); 102 | } 103 | 104 | /** 105 | * @return bool 106 | */ 107 | public function isDisplayAddToCart(): bool 108 | { 109 | return (bool)$this->getBlockModelData('display_add_to_cart', 'getDisplayAddToCart'); 110 | } 111 | 112 | /** 113 | * @param $key 114 | * @param $method 115 | * @return array|mixed|null 116 | */ 117 | protected function getBlockModelData($key, $method) 118 | { 119 | if (null === $this->getData($key)) { 120 | if ($rule = $this->getRule()) { 121 | $this->setData($key, $rule->$method() ?: ''); 122 | } else { 123 | $this->setData($key, ''); 124 | } 125 | } 126 | return $this->getData($key); 127 | } 128 | 129 | /** 130 | * @return string 131 | * @throws \Magento\Framework\Exception\LocalizedException 132 | */ 133 | public function toHtml(): string 134 | { 135 | if (!$this->config->isEnabled() || !$this->getRule()) { 136 | return ''; 137 | } 138 | 139 | // since m248 to avoid Warning: Undefined array key 4 in vendor/magento/module-catalog/Pricing/Render/FinalPriceBox.php on line 161 140 | $priceRender = $this->getLayout()->getBlock('product.price.render.default'); 141 | $isProductListValOriginal = $priceRender ? $priceRender->getData('is_product_list') : false; 142 | 143 | if ($isProductListValOriginal) { 144 | $priceRender->setData('is_product_list', false); 145 | } 146 | 147 | $html = parent::_toHtml(); 148 | 149 | if ($isProductListValOriginal) { 150 | $priceRender->setData('is_product_list', true); 151 | } 152 | 153 | if (!$html) { 154 | return ''; 155 | } 156 | 157 | $html = str_replace((string)__('Related Products'), (string)__($this->getTitle()), $html); 158 | 159 | $replaceFrom = $replaceTo = []; 160 | $ruleId = $this->getRule()->getId(); 161 | 162 | if (!$this->canItemsAddToCart() 163 | || 'catalog_product_view' != $this->getRequest()->getFullActionName() 164 | ) { 165 | $replaceFrom = array_merge( 166 | $replaceFrom, 167 | ['block-actions', 'field choice related'] 168 | ); 169 | $replaceTo = array_merge( 170 | $replaceTo, 171 | ['block-actions hide-by-rule-' . $ruleId, 'field choice related hide-by-rule-' . $ruleId] 172 | ); 173 | } 174 | 175 | if (!$this->canItemsAddToCart()) { 176 | 177 | $replaceFrom = array_merge( 178 | $replaceFrom, 179 | [' tocart '] 180 | ); 181 | $replaceTo = array_merge( 182 | $replaceTo, 183 | [' tocart hide-by-rule-' . $ruleId] 184 | ); 185 | } 186 | 187 | if ($replaceFrom) { 188 | 189 | $html = str_replace($replaceFrom, $replaceTo, $html); 190 | $html .= ''; 191 | } 192 | 193 | return $html; 194 | 195 | } 196 | 197 | /** 198 | * Get collection items 199 | * 200 | * @return Collection 201 | */ 202 | public function getItems() 203 | { 204 | /** 205 | * getIdentities() depends on _itemCollection populated, but it can be empty if the block is hidden 206 | * @see https://github.com/magento/magento2/issues/5897 207 | */ 208 | if ($this->_itemCollection === null) { 209 | $this->_itemCollection = 210 | $this->ruleManager->getReletedProductsColletion( 211 | $this->getRule(), 212 | [ 213 | 'current_category' => $this->getCategory(), 214 | 'current_product' => $this->getProduct(), 215 | 'skip_ids' => $this->getSkipItems() 216 | ] 217 | ); 218 | } 219 | 220 | return $this->_itemCollection; 221 | } 222 | 223 | /** 224 | * Get skip product ids 225 | * 226 | * @return array 227 | */ 228 | protected function getSkipItems() 229 | { 230 | if ('catalog_category_view' !== $this->getRequest()->getFullActionName()) { 231 | return []; 232 | } 233 | 234 | $productListBlock = $this->getLayout()->getBlock('category.products.list'); 235 | if (!$productListBlock) { 236 | return []; 237 | } 238 | 239 | $toolbar = $productListBlock->getToolbarBlock(); 240 | if (!$toolbar) { 241 | return []; 242 | } 243 | 244 | $pagerBlock = $toolbar->getChildBlock('product_list_toolbar_pager'); 245 | if (!($pagerBlock instanceof \Magento\Theme\Block\Html\Pager)) { 246 | return []; 247 | } 248 | 249 | $originCollection = $productListBlock->getLayer()->getProductCollection(); 250 | $collection = clone $originCollection; 251 | 252 | 253 | $pagerBlock->setLimit((int)$toolbar->getLimit()) 254 | ->setCollection($collection); 255 | 256 | return $collection->load()->getAllIds(); 257 | } 258 | 259 | /** 260 | * Synonim to getItems 261 | * 262 | * @return Collection 263 | */ 264 | public function getItemCollection() 265 | { 266 | return $this->getItems(); 267 | } 268 | 269 | /** 270 | * Check if there is any items 271 | * 272 | * @return bool 273 | */ 274 | public function hasItems(): bool 275 | { 276 | return count($this->getItems()) ? true : false; 277 | } 278 | 279 | public function getIdentities() 280 | { 281 | $identities = []; 282 | 283 | if ($this->getProduct()) { 284 | $identities = [Product::CACHE_TAG . '_' . $this->getProduct()->getId()]; 285 | } 286 | 287 | if (count($this->getItems())) { 288 | foreach ($this->getItems() as $item) { 289 | foreach ($item->getIdentities() as $identity) { 290 | $identities[] = $identity; 291 | } 292 | } 293 | } else { 294 | $identities[] = Product::CACHE_TAG; 295 | } 296 | 297 | return $identities; 298 | } 299 | 300 | /** 301 | * @return string 302 | */ 303 | public function getType() 304 | { 305 | return $this->getBlockModelData('items_type', 'getItemsType') ?: 'related'; 306 | } 307 | 308 | /** 309 | * @return bool 310 | */ 311 | public function canItemsAddToCart(): bool 312 | { 313 | return $this->getBlockModelData('display_add_to_cart', 'getDisplayAddToCart') ? true : false; 314 | } 315 | 316 | /** 317 | * Retrieve currently viewed category object 318 | * 319 | * @return \Magento\Catalog\Model\Category 320 | */ 321 | public function getCategory() 322 | { 323 | if (!$this->hasData('category')) { 324 | $this->setData('category', $this->_coreRegistry->registry('current_category')); 325 | } 326 | return $this->getData('category'); 327 | } 328 | 329 | /** 330 | * @return array|mixed|null 331 | * @throws \Magento\Framework\Exception\LocalizedException 332 | */ 333 | public function getRule() 334 | { 335 | $rule = $this->getData('rule'); 336 | 337 | $ruleId = ($rule && $rule->getId()) ? $rule->getId() : $this->getData('rule_id'); 338 | 339 | if ($ruleId) { 340 | $rule = $this->ruleManager->getRuleById((int)$ruleId); 341 | 342 | $this->setData('rule', $rule); 343 | } 344 | 345 | return $this->getData('rule'); 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /Console/Command/ApplyRules.php: -------------------------------------------------------------------------------- 1 | config = $config; 61 | $this->autoRelatedProductAction = $autoRelatedProductAction; 62 | $this->escaper = $escaper; 63 | $this->state = $state; 64 | parent::__construct($name); 65 | } 66 | 67 | /** 68 | * @param InputInterface $input 69 | * @param OutputInterface $output 70 | * @return int 71 | * @throws \Magento\Framework\Exception\LocalizedException 72 | */ 73 | protected function execute(InputInterface $input, OutputInterface $output): int 74 | { 75 | if ($this->config->isEnabled()) { 76 | try { 77 | $this->state->setAreaCode(Area::AREA_GLOBAL); 78 | } catch (LocalizedException $e) { 79 | $output->writeln((string)__('Something went wrong. %1', $this->escaper->escapeHtml($e->getMessage()))); 80 | } 81 | 82 | $ruleIDs = (string)$input->getOption(self::RULE_IDS_PARAM); 83 | 84 | $ruleIDs = $ruleIDs 85 | ? array_map('intval', explode(',', $ruleIDs)) 86 | : []; 87 | 88 | if ($ruleIDs) { 89 | $output->writeln('' . __('The provided rule IDs: %1', '`' . implode(',', $ruleIDs) . '`') . ''); 90 | $this->autoRelatedProductAction->execute(['rule_ids' => $ruleIDs]); 91 | } else { 92 | $this->autoRelatedProductAction->execute(); 93 | } 94 | 95 | $output->writeln("Rules have been applied."); 96 | } else { 97 | $output->writeln("Extension is disabled. Please turn on it."); 98 | } 99 | return 0; 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | protected function configure() 106 | { 107 | $options = [ 108 | new InputOption( 109 | self::RULE_IDS_PARAM, 110 | null, 111 | InputOption::VALUE_OPTIONAL, 112 | 'Rule Ids' 113 | ) 114 | ]; 115 | 116 | $this->setDefinition($options); 117 | 118 | $this->setName("magefan:arp:apply"); 119 | $this->setDescription("Apply by Rule IDs (comma separated)"); 120 | 121 | parent::configure(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Actions.php: -------------------------------------------------------------------------------- 1 | getRequest()->getParam('id'); 26 | $formName = $this->getRequest()->getParam('form_namespace'); 27 | $typeArr = explode('|', str_replace('-', '/', $this->getRequest()->getParam('type'))); 28 | $type = $typeArr[0]; 29 | $model = $this->_objectManager->create($type) 30 | ->setId($id) 31 | ->setType($type) 32 | ->setRule($this->_objectManager->create(\Magento\CatalogRule\Model\Rule::class)) 33 | ->setPrefix('actions'); 34 | 35 | if (!empty($typeArr[1])) { 36 | $model->setAttribute($typeArr[1]); 37 | } 38 | 39 | if ($model instanceof AbstractCondition) { 40 | $model->setJsFormObject($this->getRequest()->getParam('form')); 41 | $model->setFormName($formName); 42 | $html = $model->asHtmlRecursive(); 43 | } else { 44 | $html = ''; 45 | } 46 | $this->getResponse()->setBody($html); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Rule.php: -------------------------------------------------------------------------------- 1 | config = $config; 53 | $this->ruleCollection = $ruleCollectionFactory; 54 | $this->autoRelatedProductAction = $autoRelatedProductAction; 55 | parent::__construct($context); 56 | } 57 | 58 | /** 59 | * Action execute 60 | * @return \Magento\Framework\Controller\ResultInterface 61 | */ 62 | public function execute() 63 | { 64 | if (!$this->config->isEnabled()) { 65 | $this->messageManager->addError( 66 | __( 67 | strrev( 68 | 'noitalsnarT> snoisnetxE nafegaM > noitarugifnoC > 69 | serotS ot etagivan esaelp noisnetxe eht elbane ot ,delbasid si noitalsnarT nafegaM' 70 | ) 71 | ) 72 | ); 73 | $redirect = $this->resultRedirectFactory->create(); 74 | return $redirect->setPath('admin/index/index'); 75 | } 76 | $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); 77 | $resultRedirect->setUrl($this->_redirect->getRefererUrl()); 78 | 79 | try { 80 | $countRules = $this->ruleCollection->create()->getSize(); 81 | 82 | if (!$countRules) { 83 | $this->messageManager->addError(__('Cannot find any rule.')); 84 | } 85 | if ($this->config->isEnabled()) { 86 | $this->autoRelatedProductAction->execute(); 87 | $this->messageManager->addSuccess(__('Rules has been applied.')); 88 | } else { 89 | $this->messageManager->addNotice(__('Please enable the extension to apply rules.')); 90 | } 91 | } catch (\Exception $e) { 92 | $this->messageManager->addError(__('Something went wrong. %1', $e->getMessage())); 93 | } 94 | 95 | return $resultRedirect; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Rule/Delete.php: -------------------------------------------------------------------------------- 1 | jsonFactory = $jsonFactory; 38 | } 39 | 40 | /** 41 | * @return $this|\Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface 42 | */ 43 | public function execute() 44 | { 45 | /** @var \Magento\Framework\Controller\Result\Json $resultJson */ 46 | $resultJson = $this->jsonFactory->create(); 47 | $error = false; 48 | $messages = []; 49 | 50 | if ($this->getRequest()->getParam('isAjax')) { 51 | $ruleItems = $this->getRequest()->getParam('items', []); 52 | if (!count($ruleItems)) { 53 | $messages[] = __('Please correct the data sent.'); 54 | $error = true; 55 | } else { 56 | foreach (array_keys($ruleItems) as $modelid) { 57 | /** @var \Magento\Cms\Model\Block $block */ 58 | $model = $this->_objectManager->create('Magefan\AutoRelatedProduct\Model\Rule')->load($modelid); 59 | try { 60 | $model->setData(array_merge($model->getData(), $ruleItems[$modelid])); 61 | $model->save(); 62 | } catch (\Exception $e) { 63 | $messages[] = "[Rule ID: {$modelid}] {$e->getMessage()}"; 64 | $error = true; 65 | } 66 | } 67 | } 68 | } 69 | 70 | return $resultJson->setData([ 71 | 'messages' => $messages, 72 | 'error' => $error 73 | ]); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Rule/MassDelete.php: -------------------------------------------------------------------------------- 1 | filter = $filter; 44 | $this->collectionFactory = $collectionFactory; 45 | } 46 | /** 47 | * Execute action 48 | * 49 | * @return \Magento\Backend\Model\View\Result\Redirect 50 | * @throws \Magento\Framework\Exception\LocalizedException|\Exception 51 | */ 52 | public function execute() 53 | { 54 | $collection = $this->filter->getCollection($this->collectionFactory->create()); 55 | $collectionSize = $collection->getSize(); 56 | foreach ($collection as $item) { 57 | $item->delete(); 58 | } 59 | 60 | $this->messageManager->addSuccess(__('A total of %1 record(s) have been deleted.', $collectionSize)); 61 | 62 | /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ 63 | $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); 64 | return $resultRedirect->setPath('*/*/'); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Rule/MassStatus.php: -------------------------------------------------------------------------------- 1 | collectionFactory = $collectionFactory; 51 | $this->filter = $filter; 52 | parent::__construct($context, $dataPersistor); 53 | } 54 | 55 | /** 56 | * Action execute 57 | * @return \Magento\Framework\Controller\ResultInterface 58 | */ 59 | public function execute() 60 | { 61 | if (!$this->getRequest()->isPost()) { 62 | throw new NotFoundException(__('Page not found')); 63 | } 64 | 65 | $collection = $this->filter->getCollection($this->collectionFactory->create()); 66 | $ruleIds = $collection->getAllIds(); 67 | $status = (int) $this->getRequest()->getParam('status'); 68 | 69 | try { 70 | foreach ($ruleIds as $id) { 71 | $model = $this->_objectManager->create($this->_modelClass) 72 | ->load($id); 73 | if ($model->getId()) { 74 | $model->setData('status', $status) 75 | ->save(); 76 | } 77 | } 78 | $this->messageManager->addSuccessMessage( 79 | __('A total of %1 record(s) have been updated.', count($ruleIds)) 80 | ); 81 | } catch (\Magento\Framework\Exception\LocalizedException $e) { 82 | $this->messageManager->addErrorMessage($e->getMessage()); 83 | } catch (\Exception $e) { 84 | $this->_getSession()->addException($e, __('Something went wrong while updating the rule(s) status.')); 85 | } 86 | 87 | $this->_redirect('*/*'); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Rule/NewAction.php: -------------------------------------------------------------------------------- 1 | config = $config; 32 | $this->autoRelatedProductAction = $autoRelatedProductAction; 33 | } 34 | 35 | /** 36 | * 37 | */ 38 | public function execute() 39 | { 40 | if ($this->config->isEnabled()) { 41 | $this->autoRelatedProductAction->execute(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Please visit Magefan.com for license details (https://magefan.com/end-user-license-agreement) 2 | -------------------------------------------------------------------------------- /Model/ActionValidator.php: -------------------------------------------------------------------------------- 1 | date = $date; 78 | $this->checkoutSession = $checkoutSession; 79 | $this->ruleFactory = $ruleFactory; 80 | $this->productRepository =$productRepository; 81 | $this->request = $request; 82 | $this->registry = $registry; 83 | $this->urlInterface = $urlInterface ?: \Magento\Framework\App\ObjectManager::getInstance()->get(UrlInterface::class); 84 | } 85 | 86 | /** 87 | * @param \Magento\Framework\Model\AbstractModel $rule 88 | * @return bool 89 | * @throws \Magento\Framework\Exception\LocalizedException 90 | * @throws \Magento\Framework\Exception\NoSuchEntityException 91 | */ 92 | public function isRestricted(\Magento\Framework\Model\AbstractModel $rule): bool 93 | { 94 | if (null !== $rule->getData('is_actions_restricted')) { 95 | return $rule->getData('is_actions_restricted'); 96 | } 97 | 98 | if (!$this->isConditionsTrue($rule)) { 99 | $rule->setData('is_actions_restricted', true); 100 | return true; 101 | } 102 | $rule->setData('is_actions_restricted', false); 103 | return false; 104 | } 105 | 106 | /** 107 | * @param $actionRule 108 | * @return bool 109 | * @throws \Magento\Framework\Exception\LocalizedException 110 | * @throws \Magento\Framework\Exception\NoSuchEntityException 111 | */ 112 | public function isConditionsTrue($actionRule): bool 113 | { 114 | if (!$actionRule->getData('actions_serialized')) { 115 | return true; 116 | } 117 | 118 | $rule = $this->ruleFactory->create(); 119 | $rule->setData('conditions_serialized', $actionRule->getData('actions_serialized')); 120 | 121 | $conditions = $rule->getConditions(); 122 | 123 | if (empty($conditions['conditions'])) { 124 | return true; 125 | } 126 | 127 | $quote = $this->checkoutSession->getQuote(); 128 | 129 | if ($quote->getItemsQty() == $quote->getItemVirtualQty()) { 130 | $address = $quote->getBillingAddress(); 131 | } else { 132 | $address = $quote->getShippingAddress(); 133 | } 134 | 135 | $address->setTotalQty($quote->getItemsQty()); 136 | 137 | $product = $actionRule->getProduct(); 138 | 139 | if (!$product) { 140 | $product = $this->registry->registry('product'); 141 | } 142 | 143 | $isMfcmsdrGetController = ($this->request->getFullActionName() == 'autorp_block_get'); 144 | 145 | if (!$product) { 146 | try { 147 | $productId = $this->request->getParam('product_id'); 148 | if ($productId) { 149 | $product = $this->productRepository->getById($productId); 150 | if ($isMfcmsdrGetController) { 151 | $this->registry->register('product', $product); 152 | } 153 | } else { 154 | $product = false; 155 | } 156 | } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { 157 | $product = false; 158 | } 159 | } 160 | 161 | $address = clone $address; 162 | 163 | if ($product && $product->getId()) { 164 | foreach ($product->getData() as $k => $v) { 165 | if (!$address->getData($k)) { 166 | $address->setData($k, $v); 167 | } 168 | if ($k == 'quantity_and_stock_status') { 169 | if ($v['is_in_stock'] == false) { 170 | $address->setData('quantity_and_stock_status', 0); 171 | } else { 172 | $address->setData('quantity_and_stock_status', 1); 173 | } 174 | } 175 | } 176 | } 177 | 178 | if ($isMfcmsdrGetController) { 179 | $address->setData('page_action_name', $this->request->getParam('fan')); 180 | $address->setData('page_uri', $this->request->getParam('p')); 181 | 182 | if ($categoryId = $this->request->getParam('category_id')) { 183 | $address->setData('catalog_category_ids', $categoryId); 184 | } 185 | } else { 186 | $address->setData('page_action_name', $this->request->getFullActionName()); 187 | $address->setData('page_uri', $this->urlInterface->getCurrentUrl()); 188 | 189 | if ($this->request->getFullActionName() == 'catalog_category_view') { 190 | $address->setData('catalog_category_ids', $this->request->getParam('id')); 191 | } 192 | } 193 | 194 | return $rule->validate($address); 195 | } 196 | 197 | /** 198 | * @param $current 199 | * @param $start 200 | * @param $finish 201 | * @return bool 202 | */ 203 | public function isInTimeFrame($current, $start, $finish): bool 204 | { 205 | if ($start != $finish) { 206 | if ($start && $finish) { 207 | return ($current >= $start && $current <= $finish); 208 | } elseif ($start) { 209 | return ($start <= $current); 210 | } elseif ($finish) { 211 | return ($finish >= $current); 212 | } 213 | } 214 | return true; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Model/AutoRelatedProductAction.php: -------------------------------------------------------------------------------- 1 | resourceConnection = $resourceConnection; 116 | $this->connection = $resourceConnection->getConnection(); 117 | $this->ruleCollectionFactory = $ruleCollectionFactory; 118 | $this->ruleRepository = $ruleRepository; 119 | $this->productCollectionFactory = $productCollectionFactory; 120 | $this->validationFilter = $validationFilter; 121 | $this->sqlBuilder = $sqlBuilder ?: \Magento\Framework\App\ObjectManager::getInstance() 122 | ->get(Builder::class); 123 | $this->catalogRuleFactory = $catalogRuleFactory; 124 | 125 | $this->getParentProductIds = $getParentProductIds; 126 | $this->getWebsitesMap = $getWebsitesMap; 127 | $this->moduleManager = $moduleManager; 128 | $this->eventManager = $eventManager; 129 | 130 | if ($this->moduleManager->isEnabled('Magefan_DynamicProductAttributes')) { 131 | $this->validationFilter = 132 | \Magento\Framework\App\ObjectManager::getInstance()->get('Magefan\DynamicProductAttributes\Api\AddCustomValidationFiltersInterface'); 133 | } 134 | } 135 | 136 | /** 137 | * @param array $params 138 | * @return void 139 | */ 140 | public function execute(array $params = []): void 141 | { 142 | $productIdsToCleanCache = []; 143 | $oldProductToRuleData = []; 144 | 145 | $connection = $this->resourceConnection->getConnection(); 146 | $tableNameArpIndex = $this->resourceConnection->getTableName('magefan_autorp_index'); 147 | 148 | $ruleCollection = $this->ruleCollectionFactory->create() 149 | ->addFieldToFilter('status', 1); 150 | 151 | if (isset($params['rule_ids'])) { 152 | $ruleIds = (array)$params['rule_ids']; 153 | $ruleCollection->addFieldToFilter('id', ['in' => $ruleIds]); 154 | } 155 | 156 | if ($ruleCollection) { 157 | $oldProductToRuleCollection = $this->connection->fetchAll($this->connection->select()->from($tableNameArpIndex)); 158 | 159 | foreach ($oldProductToRuleCollection as $value) { 160 | $relatedIds = explode(',', $value['related_ids']); 161 | 162 | foreach ($relatedIds as $productId) { 163 | $oldProductToRuleData[$value['rule_id'] . '_' . $productId] = $productId; 164 | } 165 | } 166 | } 167 | 168 | foreach ($ruleCollection as $item) { 169 | if ($conditionsSerialized = $item->getData('conditions_serialized')) { 170 | $ruleId = $item->getId(); 171 | 172 | $rule = $this->catalogRuleFactory->create(); 173 | $rule->setData('conditions_serialized', $conditionsSerialized); 174 | $rule->setData('store_ids', $item->getStoreIds()); 175 | 176 | $relatedIds = $this->getListProductIds($rule); 177 | 178 | foreach ($relatedIds as $productId) { 179 | if (!isset($oldProductToRuleData[$ruleId . '_' . $productId])) { 180 | $productIdsToCleanCache[$productId] = $productId; 181 | } else { 182 | unset($oldProductToRuleData[$ruleId . '_' . $productId]); 183 | } 184 | } 185 | 186 | $connection->insertOnDuplicate( 187 | $tableNameArpIndex, 188 | [ 189 | 'rule_id' => $ruleId, 190 | 'identifier' => $item->getRuleBlockIdentifier(), 191 | 'related_ids' => implode(',', $relatedIds), 192 | ], 193 | [ 194 | 'rule_id', 195 | 'identifier', 196 | 'related_ids' 197 | ] 198 | ); 199 | } 200 | } 201 | 202 | foreach ($oldProductToRuleData as $productId) { 203 | $productIdsToCleanCache[$productId] = $productId; 204 | } 205 | 206 | if ($productIdsToCleanCache) { 207 | $this->cleanCacheByProductIds($productIdsToCleanCache); 208 | } 209 | 210 | } 211 | 212 | /** 213 | * @param $rule 214 | * @param null $params 215 | * @return array 216 | * @throws LocalizedException 217 | */ 218 | /** 219 | * @param $rule 220 | * @param null $params 221 | * @return array 222 | */ 223 | public function getListProductIds($rule) 224 | { 225 | $this->productIds = []; 226 | $conditions = $rule->getConditions(); 227 | 228 | if (!empty($conditions['conditions'])) { 229 | if ($rule->getWebsiteIds()) { 230 | $storeIds = []; 231 | $websites = $this->getWebsitesMap->execute(); 232 | 233 | foreach ($websites as $websiteId => $defaultStoreId) { 234 | if (in_array($websiteId, $rule->getWebsiteIds())) { 235 | $storeIds[] = $defaultStoreId; 236 | } 237 | } 238 | } else { 239 | $storeIds = [0]; 240 | } 241 | 242 | $conditions = $rule->getConditions()->asArray(); 243 | 244 | if ($this->validationFilter !== null) { 245 | $conditions = $this->validationFilter->processCustomValidator($conditions); 246 | } 247 | 248 | $rule->getConditions()->setConditions([])->loadArray($conditions); 249 | 250 | foreach ($storeIds as $storeId) { 251 | 252 | $productCollection = $this->productCollectionFactory->create(); 253 | 254 | if ($storeId) { 255 | $productCollection->setStoreId($storeId); 256 | } 257 | 258 | $conditions = $rule->getConditions(); 259 | 260 | $conditions->collectValidatedAttributes($productCollection); 261 | $this->sqlBuilder->attachConditionToCollection($productCollection, $conditions); 262 | 263 | if ($this->validationFilter !== null) { 264 | $this->validationFilter->addCustomValidationFilters($productCollection); 265 | } 266 | 267 | $productCollection->getSelect()->group('e.entity_id'); 268 | 269 | foreach ($productCollection as $item) { 270 | $this->productIds[] = (int) $item->getId(); 271 | } 272 | } 273 | } 274 | 275 | $this->productIds = array_merge( 276 | $this->productIds, 277 | $this->getParentProductIds->execute($this->productIds) 278 | ); 279 | 280 | return array_unique($this->productIds); 281 | } 282 | 283 | /** 284 | * @param array $productIds 285 | * @return void 286 | */ 287 | private function cleanCacheByProductIds(array $productIds): void 288 | { 289 | $productCollection = $this->productCollectionFactory->create() 290 | ->addAttributeToFilter('entity_id', ['in' => $productIds]); 291 | 292 | foreach ($productCollection as $product) { 293 | $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $product]); 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /Model/Condition/Product.php: -------------------------------------------------------------------------------- 1 | _addSpecialAttributes($attributes); 47 | asort($attributes); 48 | $this->setAttributeOption($attributes); 49 | return $this; 50 | } 51 | 52 | /** 53 | * @param array $attributes 54 | */ 55 | protected function _addSpecialAttributes(array &$attributes) 56 | { 57 | $attributes['page_action_name'] = __('Action Name'); 58 | $attributes['page_uri'] = __('URI'); 59 | $attributes['catalog_category_ids'] = __('Category'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Model/Config.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $scopeConfig; 37 | } 38 | 39 | /** 40 | * Retrieve true if module is enabled 41 | * 42 | * @return bool 43 | */ 44 | public function isEnabled($storeId = null): bool 45 | { 46 | return (bool)$this->getConfig(self::XML_PATH_EXTENSION_ENABLED, $storeId); 47 | } 48 | 49 | /** 50 | * Retrieve store config value 51 | * @param string $path 52 | * @param null $storeId 53 | * @return mixed 54 | */ 55 | public function getConfig($path, $storeId = null) 56 | { 57 | return $this->scopeConfig->getValue( 58 | $path, 59 | ScopeInterface::SCOPE_STORE, 60 | $storeId 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Model/Config/Source/MergeType.php: -------------------------------------------------------------------------------- 1 | __('Add to Current(Native) Related Products'), 'value' => self::MERGE], 27 | ['label' => __('Add Instead Current Related Products'), 'value' => self::INSTEAD] 28 | ]); 29 | 30 | return $options; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Model/Config/Source/Positions.php: -------------------------------------------------------------------------------- 1 | options = $options; 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function toOptionArray(): array 37 | { 38 | $options = []; 39 | foreach ($this->options as $option) { 40 | $option['label'] = __($option['label']); 41 | if (isset($option['value']) && is_array($option['value'])) { 42 | $option['value'] = array_values($option['value']); 43 | foreach ($option['value'] as $key => $item) { 44 | $option['value'][$key]['label'] = __($option['value'][$key]['label']); 45 | } 46 | } 47 | $options[] = $option; 48 | } 49 | return $options; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Model/Config/Source/RelatedTemplate.php: -------------------------------------------------------------------------------- 1 | self::DEFAULT, 'label' => __('Default Related Template')], 45 | ['value' => self::COMPARE, 'label' => __('Compare Template (Extra)')], 46 | ['value' => self::FBT, 'label' => __('Frequently Bought Together Template (Extra)')], 47 | ['value' => self::CUSTOM, 'label' => __(' - Set Custom Template (Plus) - ')], 48 | ]; 49 | } 50 | 51 | /** 52 | * Get options in "key-value" format 53 | * 54 | * @return array 55 | */ 56 | public function toArray() 57 | { 58 | $array = []; 59 | foreach ($this->toOptionArray() as $item) { 60 | $array[$item['value']] = $item['label']; 61 | } 62 | return $array; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Model/Config/Source/SortBy.php: -------------------------------------------------------------------------------- 1 | self::NONE, 'label' => __('None')], 35 | ['value' => self::RANDOM, 'label' => __('Random')], 36 | ['value' => self::NAME, 'label' => __('Name (Plus)')], 37 | ['value' => self::NEWEST, 'label' => __('Newest (Plus)')], 38 | ['value' => self::PRICE_DESC, 'label' => __('Price (high to low) (Plus)')], 39 | ['value' => self::PRICE_ASC, 'label' => __('Price (low to high) (Plus)')], 40 | ['value' => self::BEST_PR_WEEK, 'label' => __('Best Sellers (QTY) Per Week (Plus)')], 41 | ['value' => self::BEST_PR_MONTH, 'label' => __('Best Sellers (QTY) Per Month (Plus)')], 42 | ['value' => self::BEST_PR_QUARTER, 'label' => __('Best Sellers (QTY) Per Three Months (Plus)')], 43 | ['value' => self::BEST_PR_YEAR, 'label' => __(' Best Sellers (QTY) Per Year (Plus)')], 44 | ]; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Model/RelatedItemsProcessor.php: -------------------------------------------------------------------------------- 1 | config = $config; 36 | $this->ruleManager = $ruleManager; 37 | } 38 | 39 | /** 40 | * @param AbstractBlock $subject 41 | * @param $result 42 | * @param $blockPosition 43 | * @return \Magefan\AutoRelatedProduct\Block\Collection|mixed 44 | * @throws \Magento\Framework\Exception\LocalizedException 45 | * @throws \Magento\Framework\Exception\NoSuchEntityException 46 | */ 47 | public function execute(AbstractBlock $subject, $result, $blockPosition) 48 | { 49 | if (!$this->config->isEnabled() || !$rule = $this->ruleManager->getRuleForPosition($blockPosition)) { 50 | return $result; 51 | } 52 | 53 | if ($rule->getMergeType() == MergeType::MERGE) { 54 | $resultCount = count($result); 55 | $limit = $rule->getNumberOfProducts(); 56 | 57 | if ($resultCount >= $limit) { 58 | return $result; 59 | } 60 | 61 | $products = $subject->getLayout()->createBlock( 62 | \Magefan\AutoRelatedProduct\Block\RelatedProductList::class 63 | )->setData('rule', $rule)->getItems(); 64 | 65 | $resultIds = []; 66 | 67 | foreach ($result as $r) { 68 | $resultIds[] = $r->getId(); 69 | } 70 | 71 | foreach ($products as $product) { 72 | if ($resultCount >= $limit) { 73 | break; 74 | } 75 | 76 | if (in_array($product->getId(), $resultIds)) { 77 | continue; 78 | } 79 | 80 | if (is_object($result)) { 81 | $result->addItem($product); 82 | } else { 83 | $result[] = $product; 84 | } 85 | 86 | $resultCount++; 87 | } 88 | } elseif ($rule->getMergeType() == MergeType::INSTEAD) { 89 | $result = $subject->getLayout()->createBlock( 90 | \Magefan\AutoRelatedProduct\Block\RelatedProductList::class 91 | )->setData('rule', $rule)->getItems(); 92 | } 93 | 94 | return $result; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Model/ResourceModel/Rule.php: -------------------------------------------------------------------------------- 1 | salesRuleFactory = $salesRuleFactory; 67 | $this->catalogRuleFactory = $catalogRuleFactory; 68 | $this->request = $request; 69 | $this->messageManager = $messageManager; 70 | $this->url = $url; 71 | parent::__construct($context); 72 | } 73 | 74 | protected function _construct() 75 | { 76 | $this->_init('magefan_autorp_rule', 'id'); 77 | } 78 | 79 | /** 80 | * Process post data before deleting 81 | * @param AbstractModel $object 82 | * @return Rule 83 | */ 84 | protected function _beforeDelete(AbstractModel $object) 85 | { 86 | $condition = ['rule_id = ?' => (int)$object->getId()]; 87 | $tables = [ 88 | 'magefan_autorp_index', 89 | 'magefan_autorp_rule_store' 90 | ]; 91 | 92 | foreach ($tables as $table) { 93 | $this->getConnection()->delete( 94 | $this->getTable($table), 95 | $condition 96 | ); 97 | } 98 | 99 | return parent::_beforeDelete($object); 100 | } 101 | 102 | /** 103 | * Get ids to which specified item is assigned 104 | * @param int $postId 105 | * @param string $tableName 106 | * @param string $field 107 | * @return array 108 | */ 109 | protected function _lookupIds($ruleId, $tableName, $field) 110 | { 111 | $adapter = $this->getConnection(); 112 | $select = $adapter->select()->from( 113 | $this->getTable($tableName), 114 | $field 115 | )->where( 116 | 'rule_id = ?', 117 | (int)$ruleId 118 | ); 119 | 120 | return $adapter->fetchCol($select); 121 | } 122 | 123 | public function lookupIds($ruleId, $tableName, $field) 124 | { 125 | return $this->_lookupIds($ruleId, $tableName, $field); 126 | } 127 | 128 | /** 129 | * Get store ids to which specified item is assigned 130 | * 131 | * @param int $postId 132 | * @return array 133 | */ 134 | public function lookupStoreIds($ruleId) 135 | { 136 | return $this->_lookupIds($ruleId, 'magefan_autorp_rule_store', 'store_id'); 137 | } 138 | 139 | /** 140 | * Update post connections 141 | * @param AbstractModel $object 142 | * @param Array $newRelatedIds 143 | * @param Array $oldRelatedIds 144 | * @param String $tableName 145 | * @param String $field 146 | * @param Array $rowData 147 | * @return void 148 | */ 149 | protected function _updateLinks(AbstractModel $object, array $newRelatedIds, array $oldRelatedIds, $tableName, $field, $rowData = []) 150 | { 151 | $table = $this->getTable($tableName); 152 | 153 | if ($object->getId() && empty($rowData)) { 154 | $currentData = $this->_lookupAll($object->getId(), $tableName, '*'); 155 | foreach ($currentData as $item) { 156 | $rowData[$item[$field]] = $item; 157 | } 158 | } 159 | 160 | $insert = $newRelatedIds; 161 | $delete = $oldRelatedIds; 162 | 163 | if ($delete) { 164 | $where = ['rule_id = ?' => (int)$object->getId(), $field.' IN (?)' => $delete]; 165 | 166 | $this->getConnection()->delete($table, $where); 167 | } 168 | 169 | if ($insert) { 170 | $data = []; 171 | foreach ($insert as $id) { 172 | $id = (int)$id; 173 | $data[] = array_merge( 174 | ['rule_id' => (int)$object->getId(), $field => $id], 175 | (isset($rowData[$id]) && is_array($rowData[$id])) ? $rowData[$id] : [] 176 | ); 177 | } 178 | /* Fix if some rows have extra data */ 179 | $allFields = []; 180 | 181 | foreach ($data as $i => $row) { 182 | foreach ($row as $key => $value) { 183 | $allFields[$key] = $key; 184 | } 185 | } 186 | 187 | foreach ($data as $i => $row) { 188 | foreach ($allFields as $key) { 189 | if (!array_key_exists($key, $row)) { 190 | $data[$i][$key] = null; 191 | } 192 | } 193 | } 194 | /* End fix */ 195 | $this->getConnection()->insertMultiple($table, $data); 196 | } 197 | } 198 | 199 | /** 200 | * Update post connections 201 | * @param AbstractModel $object 202 | * @param Array $newRelatedIds 203 | * @param Array $oldRelatedIds 204 | * @param String $tableName 205 | * @param String $field 206 | * @param Array $rowData 207 | * @return void 208 | */ 209 | public function updateLinks(AbstractModel $object, array $newRelatedIds, array $oldRelatedIds, $tableName, $field, $rowData = []) 210 | { 211 | $this->_updateLinks($object, $newRelatedIds, $oldRelatedIds, $tableName, $field, $rowData); 212 | } 213 | 214 | protected function _beforeSave(AbstractModel $object) 215 | { 216 | if (is_array($object->getData('customer_group_ids'))) { 217 | $object->setData('customer_group_ids', implode(',', $object->getData('customer_group_ids'))); 218 | } 219 | 220 | if (is_array($object->getData('category_ids'))) { 221 | $arr = $object->getData('category_ids'); 222 | 223 | if ($arr[0] == '') { 224 | unset($arr[0]); 225 | } 226 | 227 | $object->setData('category_ids', implode(',', $arr)); 228 | } 229 | 230 | /* Conditions */ 231 | if ($object->getRule('conditions')) { 232 | $catalogRule = $this->catalogRuleFactory->create(); 233 | $catalogRule->loadPost(['conditions' => $object->getRule('conditions')]); 234 | $catalogRule->beforeSave(); 235 | 236 | if ($catalogRule->getConditionsSerialized() != $object->getConditionsSerialized()) { 237 | $appyRulesLink = $this->url->getUrl('*/*/apply'); 238 | 239 | $this->messageManager->addNotice( 240 | __('You have modified conditions for "Products To Display", to apply new conditions click here', $appyRulesLink) 241 | ); 242 | } 243 | 244 | $object->setData( 245 | 'conditions_serialized', 246 | $catalogRule->getConditionsSerialized() 247 | ); 248 | } 249 | 250 | /* Actions */ 251 | if ($object->getRule('actions') && $object->getData('block_position') != 'custom') { 252 | $salesRule = $this->salesRuleFactory->create(); 253 | $salesRule->loadPost(['conditions' => $object->getRule('actions')]); 254 | $salesRule->beforeSave(); 255 | $object->setData( 256 | 'actions_serialized', 257 | $salesRule->getConditionsSerialized() 258 | ); 259 | } else { 260 | $object->setData('actions_serialized', null); 261 | } 262 | 263 | /* Store View IDs */ 264 | if (is_array($object->getStoreIds())) { 265 | $object->setStoreIds( 266 | implode(',', $object->getStoreIds()) 267 | ); 268 | } 269 | return parent::_beforeSave($object); 270 | } 271 | 272 | /** 273 | * Assign post to store views, categories, related posts, etc. 274 | * 275 | * @param AbstractModel $object 276 | * @return $this 277 | */ 278 | protected function _afterSave(AbstractModel $object) 279 | { 280 | $oldIds = (array)$this->lookupStoreIds($object->getId()); 281 | $newIds =explode(',', $object->getStoreIds()); 282 | 283 | if (!$newIds || in_array(0, $newIds)) { 284 | $newIds = [0]; 285 | } 286 | 287 | $this->_updateLinks($object, $newIds, $oldIds, 'magefan_autorp_rule_store', 'store_id'); 288 | 289 | return parent::_afterSave($object); 290 | } 291 | 292 | /** 293 | * @param AbstractModel $object 294 | * @return $this 295 | */ 296 | protected function _afterLoad(AbstractModel $object) 297 | { 298 | if ($object->getId()) { 299 | $storeIds = $this->lookupStoreIds($object->getId()); 300 | $object->setData('store_ids', $storeIds); 301 | } 302 | 303 | return parent::_afterLoad($object); 304 | } 305 | 306 | 307 | /** 308 | * Get rows to which specified item is assigned 309 | * @param int $postId 310 | * @param string $tableName 311 | * @param string $field 312 | * @return array 313 | */ 314 | protected function _lookupAll($postId, $tableName, $field) 315 | { 316 | $adapter = $this->getConnection(); 317 | 318 | $select = $adapter->select()->from( 319 | $this->getTable($tableName), 320 | $field 321 | )->where( 322 | 'rule_id = ?', 323 | (int)$postId 324 | ); 325 | 326 | return $adapter->fetchAll($select); 327 | } 328 | 329 | /** 330 | * @param $object 331 | * @return array 332 | */ 333 | public function getRelatedIds($object): array 334 | { 335 | $result =$this->_lookupIds($object->getId(), 'magefan_autorp_index', 'related_ids'); 336 | if (empty($result) || empty($result[0])) { 337 | return []; 338 | } 339 | 340 | $result = explode(',', $result[0]); 341 | return $result; 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /Model/ResourceModel/Rule/Collection.php: -------------------------------------------------------------------------------- 1 | getEvent()->getObject() in this case 34 | * 35 | * @var string 36 | */ 37 | protected $_eventObject = 'collection'; 38 | 39 | /** 40 | * Define resource model 41 | * 42 | * @return void 43 | */ 44 | 45 | /** 46 | * @var int 47 | */ 48 | protected $_storeId; 49 | 50 | protected function _construct() 51 | { 52 | $this->_init(\Magefan\AutoRelatedProduct\Model\Rule::class, \Magefan\AutoRelatedProduct\Api\RelatedResourceModelInterface::class); 53 | $this->_map['fields']['id'] = 'main_table.id'; 54 | $this->_map['fields']['store'] = 'store_table.store_id'; 55 | $this->_map['fields']['group'] = 'group_table.group_id'; 56 | } 57 | 58 | /** 59 | * Join store relation table if there is store filter 60 | * 61 | * @return void 62 | */ 63 | protected function _renderFiltersBefore() 64 | { 65 | foreach (['store', 'group'] as $key) { 66 | if ($this->getFilter($key)) { 67 | $joinOptions = new \Magento\Framework\DataObject(); 68 | $joinOptions->setData([ 69 | 'key' => $key, 70 | 'fields' => [], 71 | 'fields' => [], 72 | ]); 73 | $this->_eventManager->dispatch( 74 | 'autorp_post_collection_render_filter_join', 75 | ['join_options' => $joinOptions] 76 | ); 77 | $this->getSelect()->join( 78 | [$key . '_table' => $this->getTable('magefan_autorp_rule_' . $key)], 79 | 'main_table.id = ' . $key . '_table.rule_id', 80 | $joinOptions->getData('fields') 81 | )->group( 82 | 'main_table.id' 83 | ); 84 | } 85 | } 86 | parent::_renderFiltersBefore(); 87 | } 88 | 89 | /** 90 | * Perform operations after collection load 91 | * 92 | * @return $this 93 | */ 94 | protected function _afterLoad() 95 | { 96 | /* $items = $this->getColumnValues('id'); 97 | if (count($items)) { 98 | $connection = $this->getConnection(); 99 | $tableName = $this->getTable('magefan_autorp_rule_store'); 100 | $select = $connection->select() 101 | ->from(['cps' => $tableName]) 102 | ->where('cps.rule_id IN (?)', $items); 103 | $result = []; 104 | foreach ($connection->fetchAll($select) as $item) { 105 | if (!isset($result[$item['rule_id']])) { 106 | $result[$item['rule_id']] = []; 107 | } 108 | $result[$item['rule_id']][] = $item['store_id']; 109 | } 110 | if ($result) { 111 | foreach ($this as $item) { 112 | $ruleId = $item->getData('id'); 113 | if (!isset($result[$ruleId])) { 114 | continue; 115 | } 116 | if ($result[$ruleId] == 0) { 117 | $stores = $this->_storeManager->getStores(false, true); 118 | $storeId = current($stores)->getId(); 119 | } else { 120 | $storeId = $result[$item->getData('id')]; 121 | } 122 | $item->setData('_first_store_id', $storeId); 123 | $item->setData('store_ids', $result[$ruleId]); 124 | } 125 | } 126 | } 127 | 128 | $this->_previewFlag = false; */ 129 | return parent::_afterLoad(); 130 | } 131 | 132 | /** 133 | * Add store filter to collection 134 | * @param array|int|\Magento\Store\Model\Store $store 135 | * @param boolean $withAdmin 136 | * @return $this 137 | */ 138 | public function addStoreFilter($store, $withAdmin = true) 139 | { 140 | if ($store === null) { 141 | return $this; 142 | } 143 | 144 | if (!$this->getFlag('store_filter_added')) { 145 | $this->setFlag('store_filter_added', 1); 146 | 147 | if (is_array($store)) { 148 | foreach ($store as $k => $v) { 149 | if ($k == 'like') { 150 | if (is_object($v) && $v instanceof \Zend_Db_Expr && (string)$v == "'%0%'") { 151 | return $this; 152 | } else { 153 | $this->addFilter('store', $store, 'public'); 154 | return $this; 155 | } 156 | } 157 | } 158 | } 159 | 160 | if ($store instanceof \Magento\Store\Model\Store) { 161 | $this->_storeId = $store->getId(); 162 | $store = [$store->getId()]; 163 | } 164 | 165 | if (!is_array($store)) { 166 | $this->_storeId = $store; 167 | $store = [$store]; 168 | } 169 | 170 | if (in_array(\Magento\Store\Model\Store::DEFAULT_STORE_ID, $store)) { 171 | return $this; 172 | } 173 | 174 | if ($withAdmin) { 175 | $store[] = \Magento\Store\Model\Store::DEFAULT_STORE_ID; 176 | } 177 | 178 | $this->addFilter('store', ['in' => $store], 'public'); 179 | } 180 | 181 | return $this; 182 | } 183 | 184 | /** 185 | * Add customer group filter to collection 186 | * @param array|int|null $groupId 187 | * @return $this 188 | */ 189 | public function addGroupFilter($groupId = null) 190 | { 191 | if (!$this->getFlag('group_filter_added') && $groupId !== null) { 192 | $this->addFilter('group', ['in' => $groupId], 'public'); 193 | $this->setFlag('group_filter_added', true); 194 | } 195 | 196 | return $this; 197 | } 198 | 199 | /** 200 | * Add field filter to collection 201 | * 202 | * @param string|array $field 203 | * @param null|string|array $condition 204 | * @return $this 205 | */ 206 | public function addFieldToFilter($field, $condition = null) 207 | { 208 | if (is_array($field)) { 209 | if (count($field) > 1) { 210 | return parent::addFieldToFilter($field, $condition); 211 | } elseif (count($field) === 1) { 212 | $field = $field[0]; 213 | $condition = isset($condition[0]) ? $condition[0] : $condition; 214 | } 215 | } 216 | 217 | if ($field === 'store_id' || $field === 'store_ids') { 218 | return $this->addStoreFilter($condition); 219 | } 220 | return parent::addFieldToFilter($field, $condition); 221 | } 222 | 223 | /** 224 | * Add status filter to collection 225 | * @return $this 226 | */ 227 | public function addActiveFilter() 228 | { 229 | return $this->addFieldToFilter('status', 1); 230 | } 231 | 232 | /** 233 | * @param $blockPosition 234 | * @return $this 235 | */ 236 | public function addPositionFilter($blockPosition) 237 | { 238 | return $this->addFieldToFilter('block_position', $blockPosition); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /Model/Rule.php: -------------------------------------------------------------------------------- 1 | getEvent()->getObject() in this case 30 | * 31 | * @var string 32 | */ 33 | protected $_eventObject = 'rule'; 34 | 35 | /** 36 | * @param \Magento\Framework\Model\Context $context 37 | * @param \Magento\Framework\Registry $registry 38 | * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource 39 | * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection 40 | * @param array $data 41 | */ 42 | public function __construct( 43 | \Magento\Framework\Model\Context $context, 44 | \Magento\Framework\Registry $registry, 45 | ?\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, 46 | ?\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, 47 | array $data = [] 48 | ) { 49 | parent::__construct($context, $registry, $resource, $resourceCollection, $data); 50 | } 51 | 52 | /** 53 | * @return void 54 | */ 55 | protected function _construct() 56 | { 57 | $this->_init(\Magefan\AutoRelatedProduct\Api\RelatedResourceModelInterface::class); 58 | } 59 | 60 | /** 61 | * Retrieve model title 62 | * @param boolean $plural 63 | * @return string 64 | */ 65 | public function getOwnTitle($plural = false) 66 | { 67 | return $plural ? __('Auto Related Product Rules') : __('Auto Related Product Rule'); 68 | } 69 | 70 | /** 71 | * @return array|mixed|null 72 | */ 73 | public function getId() 74 | { 75 | return $this->getData('id'); 76 | } 77 | 78 | /** 79 | * @param string $id 80 | * @return RuleInterface 81 | */ 82 | public function setId($id) 83 | { 84 | return $this->setData('id', $id); 85 | } 86 | 87 | /** 88 | * @param $ruleId 89 | * @return RuleInterface|Rule 90 | */ 91 | public function setRuleId($ruleId) 92 | { 93 | return $this->setData(self::RULE_ID, $ruleId); 94 | } 95 | 96 | /** 97 | * @return array|mixed|string|null 98 | */ 99 | public function getName() 100 | { 101 | return $this->getData('name'); 102 | } 103 | 104 | /** 105 | * @param $name 106 | * @return Rule|mixed 107 | */ 108 | public function setName($name) 109 | { 110 | return $this->setData('name', $name); 111 | } 112 | 113 | /** 114 | * @return array|mixed|null 115 | */ 116 | public function getDescription() 117 | { 118 | return $this->getData('description'); 119 | } 120 | 121 | /** 122 | * @return array|mixed|null 123 | */ 124 | public function getStatus() 125 | { 126 | return $this->getData('status'); 127 | } 128 | 129 | 130 | 131 | /** 132 | * @return false|string|string[]|null 133 | */ 134 | public function getStoreIds() 135 | { 136 | return $this->getData('store_ids'); 137 | } 138 | 139 | /** 140 | * @param $storeIds 141 | * @return Rule|mixed 142 | */ 143 | public function setStoreIds($storeIds) 144 | { 145 | return $this->setData('store_ids', $storeIds); 146 | } 147 | 148 | /** 149 | * @return array|mixed|null 150 | */ 151 | public function getPriority() 152 | { 153 | return $this->getData('priority'); 154 | } 155 | 156 | /** 157 | * @return array|mixed|null 158 | */ 159 | public function getDisplayContainer() 160 | { 161 | return $this->getData('block_position'); 162 | } 163 | 164 | /** 165 | * @return array|mixed|null 166 | */ 167 | public function getConditions() 168 | { 169 | return $this->getData('conditions_serialized'); 170 | } 171 | 172 | /** 173 | * @return array|mixed|null 174 | */ 175 | public function getActions() 176 | { 177 | return $this->getData('actions_serialized'); 178 | } 179 | 180 | /** 181 | * @return $this 182 | */ 183 | public function getDataModel() 184 | { 185 | return $this; 186 | } 187 | 188 | /** 189 | * @return bool 190 | */ 191 | public function isActive(): bool 192 | { 193 | return ($this->getStatus() == self::STATUS_ENABLED); 194 | } 195 | 196 | /** 197 | * @param $storeId 198 | * @return bool 199 | */ 200 | public function isVisibleOnStore($storeId): bool 201 | { 202 | return $this->isActive() 203 | && (null === $storeId || array_intersect([0, $storeId], $this->getStoreIds())); 204 | } 205 | 206 | /** 207 | * @return string 208 | */ 209 | public function getRuleBlockIdentifier(): string 210 | { 211 | $identifier = $this->getBlockPosition(); 212 | 213 | if ((0 !== $this->getData('from_one_category_only') || 0 !== $this->getData('only_with_higher_price')) && 'custom' != $this->getBlockPosition()) { 214 | $identifier .= '_' . '1'; 215 | 216 | } 217 | if ($this->getId()) { 218 | $identifier .= '_' . $this->getId(); 219 | } 220 | 221 | return $identifier; 222 | } 223 | 224 | /** 225 | * @return array|mixed|null 226 | */ 227 | public function getRelatedIds() 228 | { 229 | $key = 'related_ids'; 230 | 231 | if (!$this->hasData($key)) { 232 | $ids = $this->getResource()->getRelatedIds($this); 233 | $this->setData($key, $ids); 234 | } 235 | 236 | return $this->getData($key); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /Model/RuleManager.php: -------------------------------------------------------------------------------- 1 | productCollectionFactory = $productCollectionFactory; 106 | $this->catalogConfig = $catalogConfig; 107 | $this->catalogProductVisibility = $catalogProductVisibility; 108 | $this->stockFilter = $stockFilter; 109 | $this->_eventManager = $_eventManager; 110 | $this->ruleRepository = $ruleRepository; 111 | $this->storeManager = $storeManager; 112 | $this->ruleValidator = $ruleValidator; 113 | $this->ruleCollectionFactory = $ruleCollectionFactory; 114 | $this->getCategoryByProduct = $getCategoryByProduct ?:\Magento\Framework\App\ObjectManager::getInstance() 115 | ->get(\Magefan\Community\Api\GetCategoryByProductInterface::class); 116 | } 117 | 118 | /** 119 | * @param $rule 120 | * @param array $params 121 | * @return array|\Magento\Catalog\Model\ResourceModel\Product\Collection 122 | */ 123 | public function getReletedProductsColletion(RuleInterface $rule, array $params = []) 124 | { 125 | if (!$rule) { 126 | return []; 127 | } 128 | 129 | $currentProduct = $params['current_product'] ?? false; 130 | $currentCategory = $params['current_category'] ?? false; 131 | $pageSize = $params['page_size'] ?? false; 132 | $currentPage = $params['current_page'] ?? false; 133 | 134 | if (!$pageSize) { 135 | $pageSize = $rule->getData('number_of_products') ?: 10; 136 | } 137 | 138 | $this->_itemCollection = $this->productCollectionFactory->create() 139 | ->addAttributeToSelect($this->catalogConfig->getProductAttributes()) 140 | ->setVisibility($this->catalogProductVisibility->getVisibleInCatalogIds()) 141 | ->addStoreFilter() 142 | ->setPageSize((int)$pageSize); 143 | 144 | if ($currentPage) { 145 | $this->_itemCollection->setCurPage((int)$currentPage); 146 | } 147 | 148 | if (!$rule->getData('display_out_of_stock')) { 149 | $this->addOutOfStockFilter($rule); 150 | } 151 | 152 | if ($relatedIds = $rule->getRelatedIds()) { 153 | $this->_itemCollection->addFieldToFilter('entity_id', ['in' => $relatedIds]); 154 | } 155 | 156 | if ($currentProduct) { 157 | $this->_itemCollection->addFieldToFilter('entity_id', ['neq' => $currentProduct->getId()]); 158 | } 159 | 160 | if (!empty($params['skip_ids']) && is_array($params['skip_ids'])) { 161 | $this->_itemCollection->addFieldToFilter('entity_id', ['nin' => $params['skip_ids']]); 162 | } 163 | 164 | $this->addSortBy((int)$rule->getData('sort_by')); 165 | 166 | $this->_eventManager->dispatch('autorp_relatedproducts_block_load_collection_before', [ 167 | 'rule' => $rule, 168 | 'collection' => $this->_itemCollection, 169 | 'product' => $currentProduct, 170 | 'category' => $currentCategory 171 | ]); 172 | 173 | $this->_itemCollection->load(); 174 | 175 | foreach ($this->_itemCollection as $item) { 176 | $item->setDoNotUseCategoryId(true); 177 | } 178 | 179 | return $this->_itemCollection; 180 | } 181 | 182 | /** 183 | * @param RuleInterface $rule 184 | */ 185 | protected function addOutOfStockFilter(RuleInterface $rule): void 186 | { 187 | $this->_itemCollection->addMinimalPrice() 188 | ->addFinalPrice() 189 | ->addTaxPercents() 190 | ->addUrlRewrite(); 191 | 192 | $this->stockFilter->addInStockFilterToCollection($this->_itemCollection); 193 | } 194 | 195 | /** 196 | * @param $sortBy. 197 | */ 198 | protected function addSortBy(int $sortBy): void 199 | { 200 | switch ($sortBy) { 201 | case SortBy::RANDOM: 202 | $this->_itemCollection->getSelect()->order('rand()'); 203 | break; 204 | } 205 | } 206 | 207 | /** 208 | * @param int $ruleId 209 | * @return false|RuleInterface 210 | * @throws \Magento\Framework\Exception\LocalizedException 211 | */ 212 | public function getRuleById(int $ruleId) 213 | { 214 | try { 215 | $rule = $this->ruleRepository->get($ruleId); 216 | 217 | if (!$rule->isVisibleOnStore($this->storeManager->getStore()->getId()) || $this->ruleValidator->isRestricted($rule)) { 218 | $rule = false; 219 | } 220 | } catch (NoSuchEntityException $e) { 221 | $rule = false; 222 | } 223 | 224 | return $rule; 225 | } 226 | 227 | 228 | /** 229 | * @param string $blockPosition 230 | * @return false|mixed 231 | * @throws NoSuchEntityException 232 | * @throws \Magento\Framework\Exception\LocalizedException 233 | */ 234 | public function getRuleForPosition($blockPosition = '') 235 | { 236 | $rule = false; 237 | $storeId = $this->storeManager->getStore()->getId(); 238 | 239 | $rules = $this->ruleCollectionFactory->create() 240 | ->addActiveFilter() 241 | ->addPositionFilter($blockPosition) 242 | ->addStoreFilter($storeId) 243 | ->setOrder('priority', 'ASC'); 244 | 245 | foreach ($rules as $item) { 246 | if (!$this->ruleValidator->isRestricted($item)) { 247 | $rule = $item; 248 | break; 249 | } 250 | } 251 | 252 | return $rule; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /Model/RuleRepository.php: -------------------------------------------------------------------------------- 1 | resource = $resource; 114 | $this->ruleFactory = $ruleFactory; 115 | $this->ruleCollectionFactory = $ruleCollectionFactory; 116 | $this->searchResultsFactory = $searchResultsFactory; 117 | $this->dataObjectHelper = $dataObjectHelper; 118 | $this->dataRuleFactory = $dataRuleFactory; 119 | $this->dataObjectProcessor = $dataObjectProcessor; 120 | $this->storeManager = $storeManager; 121 | $this->collectionProcessor = $collectionProcessor; 122 | $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor; 123 | $this->extensibleDataObjectConverter = $extensibleDataObjectConverter; 124 | } 125 | 126 | /** 127 | * @param \Magefan\AutoRelatedProduct\Api\Data\RuleInterface $rule 128 | * @return \Magefan\AutoRelatedProduct\Api\Data\RuleInterface 129 | * @throws CouldNotSaveException 130 | */ 131 | public function save(\Magefan\AutoRelatedProduct\Api\Data\RuleInterface $rule) 132 | { 133 | try { 134 | $this->resource->save($rule); 135 | } catch (\Exception $exception) { 136 | throw new CouldNotSaveException(__( 137 | 'Could not save the rule: %1', 138 | $exception->getMessage() 139 | )); 140 | } 141 | 142 | return $rule; 143 | } 144 | 145 | /** 146 | * @param string $ruleId 147 | * @return \Magefan\AutoRelatedProduct\Api\Data\RuleInterface 148 | * @throws NoSuchEntityException 149 | */ 150 | public function get($ruleId) 151 | { 152 | $rule = $this->ruleFactory->create(); 153 | $this->resource->load($rule, $ruleId); 154 | 155 | if (!$rule->getId()) { 156 | throw new NoSuchEntityException(__('Rule with id "%1" does not exist.', $ruleId)); 157 | } 158 | return $rule; 159 | } 160 | 161 | /** 162 | * @param \Magento\Framework\Api\SearchCriteriaInterface $criteria 163 | * @return \Magefan\AutoRelatedProduct\Api\Data\RuleSearchResultsInterface 164 | */ 165 | public function getList(\Magento\Framework\Api\SearchCriteriaInterface $criteria) 166 | { 167 | $collection = $this->ruleCollectionFactory->create(); 168 | $this->extensionAttributesJoinProcessor->process( 169 | $collection, 170 | \Magefan\AutoRelatedProduct\Api\Data\RuleInterface::class 171 | ); 172 | 173 | $this->collectionProcessor->process($criteria, $collection); 174 | 175 | $searchResults = $this->searchResultsFactory->create(); 176 | $searchResults->setSearchCriteria($criteria); 177 | $items = []; 178 | 179 | foreach ($collection as $model) { 180 | $items[] = $model->getDataModel(); 181 | } 182 | 183 | $searchResults->setItems($items); 184 | $searchResults->setTotalCount($collection->getSize()); 185 | 186 | return $searchResults; 187 | } 188 | 189 | /** 190 | * @param \Magefan\AutoRelatedProduct\Api\Data\RuleInterface $rule 191 | * @return bool 192 | * @throws CouldNotDeleteException 193 | */ 194 | public function delete(\Magefan\AutoRelatedProduct\Api\Data\RuleInterface $rule) 195 | { 196 | try { 197 | $ruleModel = $this->ruleFactory->create(); 198 | $this->resource->load($ruleModel, $rule->getRuleId()); 199 | $this->resource->delete($ruleModel); 200 | } catch (\Exception $exception) { 201 | throw new CouldNotDeleteException(__( 202 | 'Could not delete the Rule: %1', 203 | $exception->getMessage() 204 | )); 205 | } 206 | 207 | return true; 208 | } 209 | 210 | /** 211 | * @param string $ruleId 212 | * @return bool 213 | * @throws CouldNotDeleteException 214 | * @throws NoSuchEntityException 215 | */ 216 | public function deleteById($ruleId) 217 | { 218 | return $this->delete($this->get($ruleId)); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /Observer/Frontend/Layout/GenerateBlocksAfter.php: -------------------------------------------------------------------------------- 1 | config = $config; 40 | $this->ruleManager = $ruleManager; 41 | } 42 | 43 | /** 44 | * @param Observer $observer 45 | * @throws \Magento\Framework\Exception\LocalizedException 46 | * @throws \Magento\Framework\Exception\NoSuchEntityException 47 | */ 48 | public function execute(Observer $observer) 49 | { 50 | if (!$this->config->isEnabled()) { 51 | return; 52 | } 53 | 54 | $block = $observer->getLayout()->getBlock(self::PARENT_BlOCK_NAME); 55 | 56 | if (!$block || !$rule = $this->ruleManager->getRuleForPosition('product_content_tab')) { 57 | return; 58 | } 59 | 60 | $block->addChild( 61 | 'autorp_tab', 62 | \Magefan\AutoRelatedProduct\Block\RelatedProductList::class, 63 | [ 64 | 'title' => $rule->getData('block_title'), 65 | 'isTab'=> 1, 66 | 'rule' => $rule 67 | ] 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Plugin/Frontend/Magento/Catalog/Block/Product/ProductList/Related.php: -------------------------------------------------------------------------------- 1 | relatedItemsProcessor = $relatedItemsProcessor; 25 | } 26 | 27 | /** 28 | * @param $subject 29 | * @param $result 30 | * @return mixed 31 | * @throws \Magento\Framework\Exception\LocalizedException 32 | * @throws \Magento\Framework\Exception\NoSuchEntityException 33 | */ 34 | public function afterGetItems($subject, $result) 35 | { 36 | return $this->relatedItemsProcessor->execute($subject, $result, 'product_into_related'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Plugin/Frontend/Magento/Catalog/Block/Product/ProductList/Upsell.php: -------------------------------------------------------------------------------- 1 | relatedItemsProcessor = $relatedItemsProcessor; 25 | } 26 | 27 | /** 28 | * @param $subject 29 | * @param $result 30 | * @return mixed 31 | * @throws \Magento\Framework\Exception\LocalizedException 32 | * @throws \Magento\Framework\Exception\NoSuchEntityException 33 | */ 34 | public function afterGetItemCollection($subject, $result) 35 | { 36 | return $this->relatedItemsProcessor->execute($subject, $result, 'product_into_upsell'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Plugin/Frontend/Magento/Catalog/Block/Product/View/Details.php: -------------------------------------------------------------------------------- 1 | config = $config; 25 | } 26 | 27 | /** 28 | * @param \Magento\Catalog\Block\Product\View\Details $subject 29 | * @param $result 30 | * @return mixed 31 | * @throws \Magento\Framework\Exception\LocalizedException 32 | */ 33 | public function afterGetGroupSortedChildNames(\Magento\Catalog\Block\Product\View\Details $subject, $result) 34 | { 35 | return ($this->config->isEnabled() && $subject->getLayout()->isBlock('product.info.details.autorp_tab')) 36 | ? array_merge($result, [45 => 'product.info.details.autorp_tab']) 37 | : $result; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Plugin/Frontend/Magento/Catalog/Model/ResourceModel/Product/Link/Product/Collection.php: -------------------------------------------------------------------------------- 1 | config = $config; 29 | } 30 | 31 | /** 32 | * @param RelatedCollection $subject 33 | * @param $result 34 | * @return mixed 35 | */ 36 | public function afterGetSize( 37 | RelatedCollection $subject, 38 | $result 39 | ) 40 | { 41 | if (!$result && $this->config->isEnabled()) { 42 | $backtrace = \Magento\Framework\Debug::backtrace(true, true, false); 43 | if (strpos($backtrace, 'Magento\Catalog\Block\Product\ProductList\Related') !== false) { 44 | $collection = clone $subject; 45 | $result = count($collection->getitems()); 46 | unset($collection); 47 | } 48 | } 49 | return $result; 50 | } 51 | } -------------------------------------------------------------------------------- /Plugin/Frontend/Magento/Framework/View/Result/Layout.php: -------------------------------------------------------------------------------- 1 | positionOptions = $positionOptions; 52 | $this->config = $config; 53 | $this->request = $request; 54 | $this->ruleManager = $ruleManager; 55 | } 56 | 57 | /** 58 | * @param Layout $subject 59 | * @param $response 60 | * @return void 61 | * @throws \Magento\Framework\Exception\LocalizedException 62 | * @throws \Magento\Framework\Exception\NoSuchEntityException 63 | */ 64 | public function beforeRenderResult(SubjectLayout $subject, $response) 65 | { 66 | if (!$this->config->isEnabled()) { 67 | return null; 68 | } 69 | 70 | /* @var $layout \Magento\Framework\View\Layout */ 71 | $layout = $subject->getLayout(); 72 | 73 | $currentLayout = (string)$this->request->getFullActionName(); 74 | 75 | $containers = [ 76 | 'content.top', 77 | 'content.bottom', 78 | ]; 79 | 80 | $parentsArray = []; 81 | $options = $this->positionOptions->toOptionArray(); 82 | 83 | for ($i = 0; $i < count($options); $i++) { 84 | if (is_array($options[$i]['value'])) { 85 | foreach ($options[$i]['value'] as $item) { 86 | if (isset($item['parent'])) { 87 | $parentsArray[$item['parent']][][$item['value']] = $item['value']; 88 | } 89 | } 90 | } 91 | } 92 | 93 | 94 | /* Fix for related producs before/after and custom theme */ 95 | $blockName = 'catalog.product.related'; 96 | if (isset($parentsArray[$blockName]) && !$layout->getBlock($blockName) && $layout->getBlock($blockName . '.theme')) { 97 | $parentsArray[$blockName . '.theme'] = $parentsArray[$blockName]; 98 | } 99 | /* End fix */ 100 | 101 | $layoutElements = []; 102 | foreach ($parentsArray as $blockName => $positions) { 103 | if ($layout->getBlock($blockName)) { 104 | $layoutElements[] = $blockName; 105 | } 106 | } 107 | 108 | foreach ($containers as $container) { 109 | if (!$layout->isContainer($container)) { 110 | continue; 111 | } 112 | $layoutElements[] = $container; 113 | } 114 | 115 | foreach ($layoutElements as $layoutElement) { 116 | foreach ($parentsArray[$layoutElement] as $position) { 117 | $containerName = $layout->getParentName($layoutElement); 118 | 119 | if (!$containerName || !$this->isBlockAvailableOnCurrentPageType($position, $currentLayout)) { 120 | continue; 121 | } 122 | 123 | if (!$rule = $this->ruleManager->getRuleForPosition($position)) { 124 | continue; 125 | } 126 | 127 | $after = (strpos($rule->getBlockPosition(), 'after') !== false); 128 | $ruleBlockName = $rule->getRuleBlockIdentifier(); 129 | $layout 130 | ->addBlock(RelatedProductList::class, $ruleBlockName, $containerName) 131 | ->setData('rule', $rule); 132 | $layout->reorderChild($containerName, $ruleBlockName, $layoutElement, $after); 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * Check block position page type is the same as current page type [category | product | cart] 139 | * @param array $position 140 | * @param string $currentLayout 141 | * @return bool 142 | */ 143 | private function isBlockAvailableOnCurrentPageType(array $position, string $currentLayout): bool 144 | { 145 | $position = array_shift($position); 146 | $position = explode('_', $position)[0]; 147 | $currentLayout = explode('_', $currentLayout)[1]; 148 | 149 | return (bool)($position === $currentLayout); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Plugin/Frontend/Magento/TargetRule/Block/Catalog/Product/ProductList/Related.php: -------------------------------------------------------------------------------- 1 | relatedItemsProcessor = $relatedItemsProcessor; 26 | } 27 | 28 | /** 29 | * @param $subject 30 | * @param $result 31 | * @return mixed 32 | * @throws \Magento\Framework\Exception\LocalizedException 33 | * @throws \Magento\Framework\Exception\NoSuchEntityException 34 | */ 35 | public function afterGetAllItems($subject, $result) 36 | { 37 | return $this->relatedItemsProcessor->execute($subject, $result, 'product_into_related'); 38 | } 39 | } -------------------------------------------------------------------------------- /Plugin/Magento/SalesRule/Model/Rule/Condition/Combine.php: -------------------------------------------------------------------------------- 1 | saleRuleFactory = $saleRuleFactory; 55 | $this->ruleAddress = $ruleAddress; 56 | $this->ruleProduct = $ruleProduct; 57 | $this->request = $request; 58 | } 59 | 60 | /** 61 | * @param Combine $subject 62 | * @param $result 63 | * @return array 64 | */ 65 | public function afterGetNewChildSelectOptions(SubjectCombine $subject, $result) 66 | { 67 | if ($this->request->getModuleName() == 'autorp') { 68 | $conditions = []; 69 | $conditions = array_merge_recursive( 70 | $conditions, 71 | [ 72 | [ 73 | 'value' => \Magento\SalesRule\Model\Rule\Condition\Product\Found::class, 74 | 'label' => __('Product attribute combination') 75 | ], 76 | [ 77 | 'value' => \Magento\SalesRule\Model\Rule\Condition\Product\Combine::class, 78 | 'label' => __('Conditions Combination') 79 | ] 80 | ] 81 | ); 82 | 83 | $productAttributes = $this->ruleProduct->create()->loadAttributeOptions()->getAttributeOption(); 84 | $attributesProduct = []; 85 | 86 | foreach ($productAttributes as $code => $label) { 87 | $attributesProduct[] = [ 88 | 'value' => 'Magento\CatalogRule\Model\Rule\Condition\Product|' . $code, 89 | 'label' => $label, 90 | ]; 91 | } 92 | 93 | $conditions = array_merge_recursive( 94 | $conditions, 95 | [ 96 | ['label' => __('Product Attribute'), 'value' => $attributesProduct] 97 | ] 98 | ); 99 | 100 | $result = $conditions; 101 | } 102 | 103 | return $result; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Plugin/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php: -------------------------------------------------------------------------------- 1 | product = $product; 35 | $this->request = $request; 36 | } 37 | 38 | public function afterGetNewChildSelectOptions(SubjectCombine $subject, $result) 39 | { 40 | if ($this->request->getModuleName() == 'autorp' || 'autorp_rule_form' == $this->request->getParam('form_namespace')) { 41 | 42 | $productAttributes = $this->product->loadAttributeOptions()->getAttributeOption(); 43 | $pAttributes = []; 44 | 45 | foreach ($productAttributes as $code => $label) { 46 | if (strpos($code, 'quote_item_') !== 0) { 47 | $pAttributes[] = [ 48 | 'value' => \Magento\SalesRule\Model\Rule\Condition\Product::class . '|' . $code, 49 | 'label' => $label, 50 | ]; 51 | } 52 | } 53 | 54 | $conditions = []; 55 | $conditions = array_merge_recursive( 56 | $conditions, 57 | [ 58 | [ 59 | 'value' => \Magento\SalesRule\Model\Rule\Condition\Product\Combine::class, 60 | 'label' => __('Conditions Combination'), 61 | ], 62 | ['label' => __('Product Attribute'), 'value' => $pAttributes] 63 | ] 64 | ); 65 | 66 | $result = $conditions; 67 | } 68 | 69 | return $result; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Ui/Component/Listing/Column/Actions.php: -------------------------------------------------------------------------------- 1 | urlBuilder = $urlBuilder; 42 | parent::__construct($context, $uiComponentFactory, $components, $data); 43 | } 44 | /** 45 | * Prepare Data Source 46 | * 47 | * @param array $dataSource 48 | * @return array 49 | */ 50 | public function prepareDataSource(array $dataSource) 51 | { 52 | if (isset($dataSource['data']['items'])) { 53 | foreach ($dataSource['data']['items'] as & $item) { 54 | $name = $this->getData('name'); 55 | 56 | if (isset($item['id'])) { 57 | $item[$name]['edit'] = [ 58 | 'href' => $this->urlBuilder->getUrl(self::URL_PATH_EDIT, ['id' => $item['id']]), 59 | 'label' => __('Edit') 60 | ]; 61 | $item[$name]['delete'] = [ 62 | 'href' => $this->urlBuilder->getUrl(self::URL_PATH_DELETE, ['id' => $item['id']]), 63 | 'label' => __('Delete'), 64 | 'confirm' => [ 65 | 'title' => __('Delete %1', $item['name']), 66 | 'message' => __('Are you sure you wan\'t to delete a %1 record?', $item['name']) 67 | ] 68 | ]; 69 | } 70 | } 71 | } 72 | 73 | return $dataSource; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Ui/DataProvider/Rule/Form/RuleDataProvider.php: -------------------------------------------------------------------------------- 1 | request = $request; 55 | $this->collection = $ruleCollection; 56 | parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); 57 | } 58 | 59 | /** 60 | * @return array 61 | */ 62 | public function getMeta() 63 | { 64 | return parent::getMeta(); 65 | } 66 | 67 | /** 68 | * Get data 69 | * 70 | * @return array 71 | */ 72 | public function getData() 73 | { 74 | if (isset($this->loadedData)) { 75 | return $this->loadedData; 76 | } 77 | 78 | $items = $this->collection->getItems(); 79 | 80 | foreach ($items as $rule) { 81 | try { 82 | $rule = $rule->load($rule->getId()); 83 | } catch (NoSuchEntityException $e) { 84 | return; 85 | } 86 | 87 | $data = $rule->getData(); 88 | 89 | /* Set data */ 90 | $this->loadedData[$rule->getId()] = $data; 91 | } 92 | 93 | return $this->loadedData; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Ui/DataProvider/Rule/Rule.php: -------------------------------------------------------------------------------- 1 | _init(Document::class, ResourceModel::class); 27 | } 28 | 29 | /** 30 | * @return \Magento\Framework\Api\Search\AggregationInterface 31 | */ 32 | public function getAggregations() 33 | { 34 | return $this->aggregations; 35 | } 36 | 37 | /** 38 | * @param \Magento\Framework\Api\Search\AggregationInterface $aggregations 39 | * @return Rule|void 40 | */ 41 | public function setAggregations($aggregations) 42 | { 43 | $this->aggregations = $aggregations; 44 | } 45 | 46 | /** 47 | * @param null $limit 48 | * @param null $offset 49 | * @return array 50 | */ 51 | public function getAllIds($limit = null, $offset = null) 52 | { 53 | return $this->getConnection()->fetchCol($this->_getAllIdsSelect($limit, $offset), $this->_bindParams); 54 | } 55 | 56 | /** 57 | * @return \Magento\Framework\Api\Search\SearchCriteriaInterface|null 58 | */ 59 | public function getSearchCriteria() 60 | { 61 | return null; 62 | } 63 | 64 | /** 65 | * @param SearchCriteriaInterface|null $searchCriteria 66 | * @return $this|Rule 67 | */ 68 | public function setSearchCriteria(?SearchCriteriaInterface $searchCriteria = null) 69 | { 70 | return $this; 71 | } 72 | 73 | /** 74 | * @return int 75 | */ 76 | public function getTotalCount() 77 | { 78 | return $this->getSize(); 79 | } 80 | 81 | /** 82 | * @param int $totalCount 83 | * @return $this|Rule 84 | */ 85 | public function setTotalCount($totalCount) 86 | { 87 | return $this; 88 | } 89 | 90 | /** 91 | * @param array|null $items 92 | * @return $this|Rule 93 | */ 94 | public function setItems(?array $items = null) 95 | { 96 | return $this; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magefan/module-auto-related-product", 3 | "description": "Extend native Magento Up-Sells, Cross-Sells, and Related products", 4 | "type": "magento2-module", 5 | "version": "2.4.4", 6 | "require": { 7 | "magefan/module-community" : ">=2.2.10" 8 | }, 9 | "autoload": { 10 | "psr-4": { 11 | "Magefan\\AutoRelatedProduct\\": "" 12 | }, 13 | "files": [ 14 | "registration.php" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /etc/adminhtml/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /etc/adminhtml/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 |
11 | separator-top 12 | 13 | magefan 14 | Magefan_AutoRelatedProduct::config 15 | 16 | 17 | 1 18 | 19 | Magefan\AutoRelatedProduct\Block\Adminhtml\System\Config\Form\Info 20 | 21 | 22 | 23 | 24 | Magento\Config\Model\Config\Source\Yesno 25 | 26 | 27 | 28 | Magefan\Community\Block\Adminhtml\System\Config\Form\ProductKeyField 29 | 30 | 31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 0 13 | AutoRelatedProduct 14 | 1 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /etc/crontab.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 20 */8 * * * 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /etc/db_schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 |
63 | -------------------------------------------------------------------------------- /etc/frontend/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /etc/frontend/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /view/adminhtml/layout/autorp_rule_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /view/adminhtml/layout/autorp_rule_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /view/adminhtml/layout/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /view/adminhtml/layout/related_product_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /view/adminhtml/templates/form/versionsManager.phtml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | get(\Magefan\Community\Api\GetModuleVersionInterface::class); 16 | 17 | $currentPlan = 'Basic'; 18 | 19 | if ($getModuleVersion->execute('Magefan_AutoRelatedProductExtra')) { 20 | return; 21 | } elseif ($getModuleVersion->execute('Magefan_AutoRelatedProductPlus')) { 22 | $currentPlan = 'Plus'; 23 | } 24 | ?> 25 | 26 | { 63 | mutations.forEach(mutation => { 64 | for (let plan in versionsManager._selector) { 65 | let planFeatures = versionsManager._selector[plan]; 66 | 67 | planFeatures.forEach(selector => { 68 | if (document.querySelector(selector)) { 69 | const element = document.querySelector(selector); 70 | 71 | if ( 72 | element.tagName == 'SELECT' 73 | && Array.from(element.options).some(option => option.text.includes('Plus') || option.text.includes('Extra')) 74 | ) { 75 | element.addEventListener('change', function() { 76 | var selectedOptionText = this.options[this.selectedIndex].text; 77 | if (selectedOptionText.includes('('+ plan +')')) { 78 | versionsManager.showAlert(plan); 79 | this.selectedIndex = 0; 80 | this.dispatchEvent(new Event('change', { bubbles: true })); 81 | } 82 | }); 83 | } else { 84 | element.addEventListener('click', function (event) { 85 | this.value = ''; 86 | 87 | if (this.tagName == 'SELECT') { 88 | this.dispatchEvent(new Event('change', { bubbles: true })); 89 | } 90 | 91 | event.preventDefault(); 92 | event.stopPropagation(); 93 | 94 | if (versionsManager._currentPlan != plan) { 95 | versionsManager.showAlert(plan) 96 | } 97 | }); 98 | } 99 | 100 | // Remove the selector from _selector 101 | versionsManager._selector[plan] = versionsManager._selector[plan].filter(item => item !== selector); 102 | } 103 | }); 104 | } 105 | }); 106 | }); 107 | 108 | // Start observing the document 109 | observer.observe(document.body, { childList: true, subtree: true }); 110 | }, 111 | 112 | showAlert: function (extensionPlan) { 113 | require(['jquery', 'Magento_Ui/js/modal/alert'], function($, alert) { 114 | if (extensionPlan === 'Plus') { 115 | extensionPlan = 'Plus or Extra'; 116 | } 117 | alert({ 118 | title: '" . $escaper->escapeHtml(__('You cannot use this option.')) . "', 119 | content: '" . $escaper->escapeHtml(__('This feature is available in')) . "' + ' ' + extensionPlan + ' " . __('plan only.') . "', 120 | buttons: [{ 121 | text: '" . $escaper->escapeHtml(__('Upgrade Plan Now')) . "', 122 | class: 'action primary accept', 123 | click: function () { 124 | window.open('https://magefan.com'); 125 | } 126 | }] 127 | }); 128 | }); 129 | } 130 | }; 131 | 132 | versionsManager.initListener(); 133 | 134 | "; 135 | ?> 136 | 137 | renderTag('script', [], $script, false) ?> 138 | -------------------------------------------------------------------------------- /view/adminhtml/templates/info.phtml: -------------------------------------------------------------------------------- 1 | 7 | 12 | isEnabled()) { ?> 13 |
14 |
15 |
16 | Stores > Configuration > Magefan Extensions > Auto Related Products. 18 | ', $block->getUrl('adminhtml/system_config/edit', ['section' => 'autorp'])); 19 | ?> 20 | 21 |
22 |
23 |
24 | 25 | 26 | 45 | 46 | -------------------------------------------------------------------------------- /view/adminhtml/templates/tmpInfo.phtml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | isSomeFeaturesRestricted() && $affectedRules = $block->getAffectedRulesByDisplayModes()) { ?> 14 |
15 |
16 |
17 |

18 | escapeHtml(__('Display Modes:')) ?> "From Same Category Only", "Only With Higher Price" escapeHtml(__('and')) ?> "Only With Lower Price" 19 | escapeHtml(__('are no longer available in the Magefan Auto Related Products Basic version.')) ?> 20 |

21 | 22 |

23 | escapeHtml(__('Please')) ?> 24 | 25 | escapeHtml(__('upgrade to Plus or Extra edition ')) ?> 26 | 27 | escapeHtml(__(' or use an older extension version up to 2.4.0 to be able to use these options.')) ?> 28 |

29 |
30 |

31 | escapeHtml(__('Affected rule IDs: %1.', $affectedRules)) ?> 32 |

33 |
34 |
35 |
36 | 37 | 38 | isSomeFeaturesRestricted() && $affectedRules = $block->getAffectedRulesBySortBy()) { ?> 39 |
40 |
41 |
42 |

43 | escapeHtml(__('Sort By:')) ?> "Name", "Newest", "Price (high to low)" escapeHtml(__('and')) ?> "Price (low to high)" 44 | escapeHtml(__('are no longer available in the Magefan Auto Related Products Basic version.')) ?> 45 |

46 | 47 |

48 | escapeHtml(__('Please')) ?> 49 | 50 | escapeHtml(__('upgrade to Plus or Extra edition ')) ?> 51 | 52 | escapeHtml(__(' or use an older extension version up to 2.4.0 to be able to use these options.')) ?> 53 |

54 |
55 |

56 | escapeHtml(__('Affected rule IDs: %1.', $affectedRules)) ?> 57 |

58 |
59 |
60 |
61 | 62 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/autorp_listing.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | autorp_listing.autorp_listing_data_source 12 | autorp_listing.autorp_listing_data_source 13 | 14 | autorp_listing_columns 15 | 16 | 17 | add 18 | Apply Rules 19 | primary 20 | */*/apply 21 | 22 | 23 | add 24 | Add New Rule 25 | primary 26 | */*/new 27 | 28 | 29 | 30 | 31 | 32 | Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider 33 | autorp_listing_data_source 34 | id 35 | id 36 | 37 | 38 | Magento_Ui/js/grid/provider 39 | 40 | 41 | id 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | true 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Magento_Ui/js/form/element/ui-select 63 | ui/grid/filters/elements/ui-select 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | Magento_Ui/js/grid/tree-massactions 74 | 75 | 76 | 77 | 78 | 79 | delete 80 | Delete 81 | 82 | 83 | Delete items 84 | Are you sure you want to delete selected items? 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | status 93 | Change status 94 | 95 | 96 | 97 | 98 | enable 99 | Enable 100 | 101 | 1 102 | 103 | 104 | 105 | disable 106 | Disable 107 | 108 | 0 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | autorp_listing.autorp_listing.autorp_listing_columns.ids 122 | true 123 | id 124 | 125 | 126 | false 127 | 131 | 132 | 133 | 134 | 135 | autorp_listing.autorp_listing.autorp_listing_columns_editor 136 | startEdit 137 | 138 | ${ $.$data.rowIndex } 139 | true 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | id 149 | 150 | 151 | 152 | 153 | 154 | 155 | ID 156 | textRange 157 | 158 | 159 | 160 | 161 | 162 | 163 | text 164 | text 165 | Rule 166 | 167 | 168 | 169 | 170 | 171 | Magento\Cms\Model\Block\Source\IsActive 172 | 173 | select 174 | Magento_Ui/js/grid/columns/select 175 | select 176 | select 177 | Status 178 | 179 | 180 | 181 | 182 | 183 | 184 | text 185 | textRange 186 | Priority 187 | asc 188 | 189 | 190 | 191 | 192 | 193 | Magento\Cms\Ui\Component\Listing\Column\Cms\Options 194 | 195 | select 196 | Magento_Ui/js/grid/columns/select 197 | Store View 198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /view/adminhtml/web/js/form/element/is-product-conditions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © Magefan (support@magefan.com). All rights reserved. 3 | * Please visit Magefan.com for license details (https://magefan.com/end-user-license-agreement). 4 | */ 5 | 6 | define([ 7 | 'jquery', 8 | 'uiRegistry', 9 | 'Magento_Ui/js/form/element/select', 10 | 'domReady!' 11 | ], function ($, uiRegistry, select) { 12 | 'use strict'; 13 | return select.extend({ 14 | defaults: { 15 | customName: '${ $.parentName }.${ $.index }_input' 16 | }, 17 | 18 | initialize: function () { 19 | this._super(); 20 | var self = this; 21 | setTimeout(function () { 22 | self.fieldDepend(self.value()) 23 | }, 1000); 24 | }, 25 | 26 | onUpdate: function(value) { 27 | this.fieldDepend(value); 28 | return this._super(); 29 | }, 30 | 31 | fieldDepend: function(value) { 32 | if (!value) { 33 | $('div[class="rule-tree"]').hide(); 34 | $('.container.sample').hide(); 35 | $('div[data-index="merge_type"]').hide(); 36 | $('button[data-index="preview_button"]').hide(); 37 | $('fieldset[data-index="container_categories"]').hide(); 38 | $('#autorp_rule_formrule_same_as_conditions_fieldset_').hide(); 39 | $('[data-index="apply_same_as_condition"]').hide(); 40 | $('[data-index="display_mode"]').hide(); 41 | return; 42 | } 43 | 44 | if (value.startsWith("custom")) { 45 | $('div[class="rule-tree"]').show(); 46 | $('.container.sample').show(); 47 | $('div[data-index="merge_type"]').hide(); 48 | $('button[data-index="preview_button"]').show(); 49 | $('fieldset[data-index="container_categories"]').hide(); 50 | $('#autorp_rule_formrule_same_as_conditions_fieldset_').show(); 51 | $('[data-index="apply_same_as_condition"]').show(); 52 | 53 | if (uiRegistry.get('autorp_rule_form.autorp_rule_form.what_to_display').source.data.apply_same_as_condition) { 54 | $('#autorp_rule_formrule_same_as_conditions_fieldset_').show(); 55 | } else { 56 | $('#autorp_rule_formrule_same_as_conditions_fieldset_').hide(); 57 | } 58 | 59 | $('[data-index="where_to_display_product"] div[class="rule-tree"]').hide(); 60 | $('[data-index="display_mode"]').show(); 61 | return; 62 | } 63 | 64 | if (value.startsWith("product_")) { 65 | $('div[class="rule-tree"]').show(); 66 | $('.container.sample').hide(); 67 | if(value.startsWith("product_into")){ 68 | $('div[data-index="merge_type"]').show(); 69 | } 70 | else{ 71 | $('div[data-index="merge_type"]').hide(); 72 | } 73 | $('button[data-index="preview_button"]').show(); 74 | $('fieldset[data-index="container_categories"]').hide(); 75 | 76 | $('[data-index="apply_same_as_condition"]').show(); 77 | 78 | if (uiRegistry.get('autorp_rule_form.autorp_rule_form.what_to_display').source.data.apply_same_as_condition) { 79 | $('#autorp_rule_formrule_same_as_conditions_fieldset_').show(); 80 | } else { 81 | $('#autorp_rule_formrule_same_as_conditions_fieldset_').hide(); 82 | } 83 | 84 | //$('[data-index="where_to_display_product"] [label="Product Attribute"], [data-index="where_to_display_product"] [value="Magento\\\\SalesRule\\\\Model\\\\Rule\\\\Condition\\\\Product\\\\Combine"]').prop( "disabled", false); 85 | $('[data-index="display_mode"]').show(); 86 | return; 87 | } 88 | 89 | if (value.startsWith("cart_")) { 90 | $('div[class="rule-tree"]').show(); 91 | $('.container.sample').hide(); 92 | if(value.startsWith("cart_into")){ 93 | $('div[data-index="merge_type"]').show(); 94 | } 95 | else{ 96 | $('div[data-index="merge_type"]').hide(); 97 | } 98 | $('button[data-index="preview_button"]').show(); 99 | $('fieldset[data-index="container_categories"]').hide(); 100 | $('#autorp_rule_formrule_same_as_conditions_fieldset_').hide(); 101 | $('[data-index="apply_same_as_condition"]').hide(); 102 | //$('[data-index="where_to_display_product"] [label="Product Attribute"], [data-index="where_to_display_product"] [value="Magento\\\\SalesRule\\\\Model\\\\Rule\\\\Condition\\\\Product\\\\Combine"]').prop( "disabled", true ); 103 | $('[data-index="display_mode"]').hide(); 104 | return; 105 | } 106 | 107 | if (value.startsWith("category_")) { 108 | $('div[class="rule-tree"]').show(); 109 | $('.container.sample').hide(); 110 | $('div[data-index="merge_type"]').hide(); 111 | $('button[data-index="preview_button"]').show(); 112 | $('fieldset[data-index="container_categories"]').show(); 113 | $('#autorp_rule_formrule_same_as_conditions_fieldset_').hide(); 114 | $('[data-index="apply_same_as_condition"]').hide(); 115 | //$('[data-index="where_to_display_product"] [label="Product Attribute"], [data-index="where_to_display_product"] [value="Magento\\\\SalesRule\\\\Model\\\\Rule\\\\Condition\\\\Product\\\\Combine"]').prop( "disabled", true ); 116 | $('[data-index="display_mode"]').hide(); 117 | return; 118 | } 119 | }, 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /view/adminhtml/web/js/lib/core/collection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © Magefan (support@magefan.com). All rights reserved. 3 | * Please visit Magefan.com for license details (https://magefan.com/end-user-license-agreement). 4 | */ 5 | 6 | define([ 7 | 'underscore', 8 | 'mageUtils', 9 | 'uiRegistry', 10 | 'uiComponent', 11 | 'jquery', 12 | 'Magento_Ui/js/lib/view/utils/async' 13 | ], function (_, utils, registry, Element, $, async) { 14 | 'use strict'; 15 | 16 | return Element.extend({ 17 | defaults: { 18 | visible: true 19 | }, 20 | 21 | /** 22 | * Called when another element was added to current component. 23 | * 24 | * @param {Object} elem - Instance of an element that was added. 25 | * @returns {Collection} Chainable. 26 | */ 27 | initElement: function (elem) { 28 | 29 | if (elem.additionalClasses) { 30 | elem.additionalClasses[this.className] = true; 31 | } 32 | 33 | elem.initContainer(this); 34 | 35 | return this; 36 | }, 37 | 38 | /** 39 | * Show element. 40 | * 41 | * @returns {Abstract} Chainable. 42 | */ 43 | show: function () { 44 | $('.' + this.className).show(); 45 | 46 | return this; 47 | }, 48 | 49 | /** 50 | * Hide element. 51 | * 52 | * @returns {Abstract} Chainable. 53 | */ 54 | hide: function () { 55 | var element = $('.' + this.className), 56 | self = this; 57 | 58 | if (element.length) { 59 | element.hide(); 60 | } else { 61 | async.async('.' + self.className, function (item) { 62 | $(item).hide(); 63 | }.bind(this)); 64 | } 65 | 66 | return this; 67 | } 68 | }); 69 | }); 70 | --------------------------------------------------------------------------------