Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Rule.php
Go to the documentation of this file.
1 <?php
7 
9 use Magento\Catalog\Model\ProductFactory;
10 use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
11 use Magento\CatalogRule\Api\Data\RuleExtensionInterface;
17 use Magento\CatalogRule\Model\Rule\Action\CollectionFactory as RuleCollectionFactory;
18 use Magento\CatalogRule\Model\Rule\Condition\CombineFactory;
37 
51 {
57  protected $_eventPrefix = 'catalogrule_rule';
58 
66  protected $_eventObject = 'rule';
67 
73  protected $_productIds;
74 
80  protected $_productsFilter = null;
81 
87  protected $_now;
88 
94  protected static $_priceRulesData = [];
95 
101  protected $_catalogRuleData;
102 
106  protected $_cacheTypesList;
107 
112 
116  protected $dateTime;
117 
122 
126  protected $_customerSession;
127 
131  protected $_combineFactory;
132 
137 
141  protected $_productFactory;
142 
146  protected $_storeManager;
147 
152 
157 
162 
166  private $conditionsToCollectionApplier;
167 
171  private $websitesMap;
172 
176  private $ruleResourceModel;
177 
208  public function __construct(
209  Context $context,
211  FormFactory $formFactory,
212  TimezoneInterface $localeDate,
213  CollectionFactory $productCollectionFactory,
215  CombineFactory $combineFactory,
216  RuleCollectionFactory $actionCollectionFactory,
217  ProductFactory $productFactory,
218  Iterator $resourceIterator,
219  Session $customerSession,
220  Data $catalogRuleData,
221  TypeListInterface $cacheTypesList,
223  RuleProductProcessor $ruleProductProcessor,
225  AbstractDb $resourceCollection = null,
226  array $relatedCacheTypes = [],
227  array $data = [],
228  ExtensionAttributesFactory $extensionFactory = null,
230  Json $serializer = null,
231  RuleResourceModel $ruleResourceModel = null,
232  ConditionsToCollectionApplier $conditionsToCollectionApplier = null
233  ) {
234  $this->_productCollectionFactory = $productCollectionFactory;
235  $this->_storeManager = $storeManager;
236  $this->_combineFactory = $combineFactory;
237  $this->_actionCollectionFactory = $actionCollectionFactory;
238  $this->_productFactory = $productFactory;
239  $this->_resourceIterator = $resourceIterator;
240  $this->_customerSession = $customerSession;
241  $this->_catalogRuleData = $catalogRuleData;
242  $this->_cacheTypesList = $cacheTypesList;
243  $this->_relatedCacheTypes = $relatedCacheTypes;
244  $this->dateTime = $dateTime;
245  $this->_ruleProductProcessor = $ruleProductProcessor;
246  $this->ruleResourceModel = $ruleResourceModel ?: ObjectManager::getInstance()->get(RuleResourceModel::class);
247 
248  $this->conditionsToCollectionApplier = $conditionsToCollectionApplier
249  ?? ObjectManager::getInstance()->get(ConditionsToCollectionApplier::class);
250 
251  parent::__construct(
252  $context,
253  $registry,
254  $formFactory,
255  $localeDate,
256  $resource,
257  $resourceCollection,
258  $data,
259  $extensionFactory,
262  );
263  }
264 
270  protected function _construct()
271  {
272  parent::_construct();
273  $this->_init(RuleResourceModel::class);
274  $this->setIdFieldName('rule_id');
275  }
276 
282  public function getConditionsInstance()
283  {
284  return $this->_combineFactory->create();
285  }
286 
292  public function getActionsInstance()
293  {
294  return $this->_actionCollectionFactory->create();
295  }
296 
302  public function getCustomerGroupIds()
303  {
304  if (!$this->hasCustomerGroupIds()) {
305  $customerGroupIds = $this->ruleResourceModel->getCustomerGroupIds($this->getId());
306  $this->setData('customer_group_ids', (array)$customerGroupIds);
307  }
308  return $this->_getData('customer_group_ids');
309  }
310 
316  public function getNow()
317  {
318  if (!$this->_now) {
319  return (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT);
320  }
321  return $this->_now;
322  }
323 
331  public function setNow($now)
332  {
333  $this->_now = $now;
334  }
335 
341  public function getMatchingProductIds()
342  {
343  if ($this->_productIds === null) {
344  $this->_productIds = [];
345  $this->setCollectedAttributes([]);
346 
347  if ($this->getWebsiteIds()) {
349  $productCollection = $this->_productCollectionFactory->create();
350  $productCollection->addWebsiteFilter($this->getWebsiteIds());
351  if ($this->_productsFilter) {
352  $productCollection->addIdFilter($this->_productsFilter);
353  }
354  $this->getConditions()->collectValidatedAttributes($productCollection);
355 
356  if ($this->canPreMapProducts()) {
357  $productCollection = $this->conditionsToCollectionApplier
358  ->applyConditionsToCollection($this->getConditions(), $productCollection);
359  }
360 
361  $this->_resourceIterator->walk(
362  $productCollection->getSelect(),
363  [[$this, 'callbackValidateProduct']],
364  [
365  'attributes' => $this->getCollectedAttributes(),
366  'product' => $this->_productFactory->create()
367  ]
368  );
369  }
370  }
371 
372  return $this->_productIds;
373  }
374 
380  private function canPreMapProducts()
381  {
382  $conditions = $this->getConditions();
383 
384  // No need to map products if there is no conditions in rule
385  if (!$conditions || !$conditions->getConditions()) {
386  return false;
387  }
388 
389  return true;
390  }
391 
398  public function callbackValidateProduct($args)
399  {
400  $product = clone $args['product'];
401  $product->setData($args['row']);
402 
403  $websites = $this->_getWebsitesMap();
404  $results = [];
405 
406  foreach ($websites as $websiteId => $defaultStoreId) {
407  $product->setStoreId($defaultStoreId);
408  $results[$websiteId] = $this->getConditions()->validate($product);
409  }
410  $this->_productIds[$product->getId()] = $results;
411  }
412 
418  protected function _getWebsitesMap()
419  {
420  if ($this->websitesMap === null) {
421  $this->websitesMap = [];
422  $websites = $this->_storeManager->getWebsites();
423  foreach ($websites as $website) {
424  // Continue if website has no store to be able to create catalog rule for website without store
425  if ($website->getDefaultStore() === null) {
426  continue;
427  }
428  $this->websitesMap[$website->getId()] = $website->getDefaultStore()->getId();
429  }
430  }
431 
432  return $this->websitesMap;
433  }
434 
438  public function validateData(DataObject $dataObject)
439  {
440  $result = parent::validateData($dataObject);
441  if ($result === true) {
442  $result = [];
443  }
444 
445  $action = $dataObject->getData('simple_action');
446  $discount = $dataObject->getData('discount_amount');
447  $result = array_merge($result, $this->validateDiscount($action, $discount));
448 
449  return !empty($result) ? $result : true;
450  }
451 
460  protected function validateDiscount($action, $discount)
461  {
462  $result = [];
463  switch ($action) {
464  case 'by_percent':
465  case 'to_percent':
466  if ($discount < 0 || $discount > 100) {
467  $result[] = __('Percentage discount should be between 0 and 100.');
468  }
469  break;
470  case 'by_fixed':
471  case 'to_fixed':
472  if ($discount < 0) {
473  $result[] = __('Discount value should be 0 or greater.');
474  }
475  break;
476  default:
477  $result[] = __('Unknown action.');
478  }
479  return $result;
480  }
481 
491  {
492  $priceRules = null;
493  $productId = $product->getId();
494  $storeId = $product->getStoreId();
495  $websiteId = $this->_storeManager->getStore($storeId)->getWebsiteId();
496  if ($product->hasCustomerGroupId()) {
497  $customerGroupId = $product->getCustomerGroupId();
498  } else {
499  $customerGroupId = $this->_customerSession->getCustomerGroupId();
500  }
501  $dateTs = $this->_localeDate->scopeTimeStamp($storeId);
502  $cacheKey = date('Y-m-d', $dateTs) . "|{$websiteId}|{$customerGroupId}|{$productId}|{$price}";
503 
504  if (!array_key_exists($cacheKey, self::$_priceRulesData)) {
505  $rulesData = $this->_getRulesFromProduct($dateTs, $websiteId, $customerGroupId, $productId);
506  if ($rulesData) {
507  foreach ($rulesData as $ruleData) {
508  if ($product->getParentId()) {
509  $priceRules = $priceRules ? $priceRules : $price;
510  if ($ruleData['action_stop']) {
511  break;
512  }
513  } else {
514  $priceRules = $this->_catalogRuleData->calcPriceRule(
515  $ruleData['action_operator'],
516  $ruleData['action_amount'],
517  $priceRules ? $priceRules : $price
518  );
519  if ($ruleData['action_stop']) {
520  break;
521  }
522  }
523  }
524  return self::$_priceRulesData[$cacheKey] = $priceRules;
525  } else {
526  self::$_priceRulesData[$cacheKey] = null;
527  }
528  } else {
529  return self::$_priceRulesData[$cacheKey];
530  }
531  return null;
532  }
533 
543  protected function _getRulesFromProduct($dateTs, $websiteId, $customerGroupId, $productId)
544  {
545  return $this->ruleResourceModel->getRulesFromProduct($dateTs, $websiteId, $customerGroupId, $productId);
546  }
547 
556  {
557  $this->_productsFilter = $productIds;
558  }
559 
566  public function getProductsFilter()
567  {
568  return $this->_productsFilter;
569  }
570 
576  protected function _invalidateCache()
577  {
578  if (count($this->_relatedCacheTypes)) {
579  $this->_cacheTypesList->invalidate($this->_relatedCacheTypes);
580  }
581  return $this;
582  }
583 
589  public function afterSave()
590  {
591  if ($this->isObjectNew() && !$this->_ruleProductProcessor->isIndexerScheduled()) {
592  $productIds = $this->getMatchingProductIds();
593  if (!empty($productIds) && is_array($productIds)) {
594  $this->ruleResourceModel->addCommitCallback([$this, 'reindex']);
595  }
596  } else {
597  $this->_ruleProductProcessor->getIndexer()->invalidate();
598  }
599  return parent::afterSave();
600  }
601 
607  public function reindex()
608  {
609  $productIds = $this->_productIds ? array_keys(array_filter($this->_productIds, function (array $data) {
610  return array_filter($data);
611  })) : [];
612  $this->_ruleProductProcessor->reindexList($productIds);
613  }
614 
620  public function afterDelete()
621  {
622  $this->_ruleProductProcessor->getIndexer()->invalidate();
623  return parent::afterDelete();
624  }
625 
631  public function isRuleBehaviorChanged()
632  {
633  if (!$this->isObjectNew()) {
634  $arrayDiff = $this->dataDiff($this->getOrigData(), $this->getStoredData());
635  unset($arrayDiff['name']);
636  unset($arrayDiff['description']);
637  if (empty($arrayDiff)) {
638  return false;
639  }
640  }
641  return true;
642  }
643 
651  protected function dataDiff($array1, $array2)
652  {
653  $result = [];
654  foreach ($array1 as $key => $value) {
655  if (array_key_exists($key, $array2)) {
656  if ($value != $array2[$key]) {
657  $result[$key] = true;
658  }
659  } else {
660  $result[$key] = true;
661  }
662  }
663  return $result;
664  }
665 
670  public function getConditionsFieldSetId($formName = '')
671  {
672  return $formName . 'rule_conditions_fieldset_' . $this->getId();
673  }
674 
675  //@codeCoverageIgnoreStart
676 
680  public function getRuleId()
681  {
682  return $this->getData(self::RULE_ID);
683  }
684 
688  public function setRuleId($ruleId)
689  {
690  return $this->setData(self::RULE_ID, $ruleId);
691  }
692 
696  public function getName()
697  {
698  return $this->getData(self::NAME);
699  }
700 
704  public function setName($name)
705  {
706  return $this->setData(self::NAME, $name);
707  }
708 
712  public function getDescription()
713  {
714  return $this->getData(self::DESCRIPTION);
715  }
716 
720  public function setDescription($description)
721  {
722  return $this->setData(self::DESCRIPTION, $description);
723  }
724 
728  public function getIsActive()
729  {
730  return $this->getData(self::IS_ACTIVE);
731  }
732 
736  public function setIsActive($isActive)
737  {
738  return $this->setData(self::IS_ACTIVE, $isActive);
739  }
740 
744  public function getRuleCondition()
745  {
746  return $this->getRuleConditionConverter()->arrayToDataModel($this->getConditions()->asArray());
747  }
748 
752  public function setRuleCondition($condition)
753  {
754  $this->getConditions()
755  ->setConditions([])
756  ->loadArray($this->getRuleConditionConverter()->dataModelToArray($condition));
757  return $this;
758  }
759 
763  public function getStopRulesProcessing()
764  {
765  return $this->getData(self::STOP_RULES_PROCESSING);
766  }
767 
771  public function setStopRulesProcessing($isStopProcessing)
772  {
773  return $this->setData(self::STOP_RULES_PROCESSING, $isStopProcessing);
774  }
775 
779  public function getSortOrder()
780  {
781  return $this->getData(self::SORT_ORDER);
782  }
783 
787  public function setSortOrder($sortOrder)
788  {
789  return $this->setData(self::SORT_ORDER, $sortOrder);
790  }
791 
795  public function getSimpleAction()
796  {
797  return $this->getData(self::SIMPLE_ACTION);
798  }
799 
803  public function setSimpleAction($action)
804  {
805  return $this->setData(self::SIMPLE_ACTION, $action);
806  }
807 
811  public function getDiscountAmount()
812  {
813  return $this->getData(self::DISCOUNT_AMOUNT);
814  }
815 
819  public function setDiscountAmount($amount)
820  {
821  return $this->setData(self::DISCOUNT_AMOUNT, $amount);
822  }
823 
827  public function getFromDate()
828  {
829  return $this->getData('from_date');
830  }
831 
835  public function getToDate()
836  {
837  return $this->getData('to_date');
838  }
839 
845  public function getExtensionAttributes()
846  {
847  return $this->_getExtensionAttributes();
848  }
849 
856  public function setExtensionAttributes(RuleExtensionInterface $extensionAttributes)
857  {
858  return $this->_setExtensionAttributes($extensionAttributes);
859  }
860 
865  private function getRuleConditionConverter()
866  {
867  if (null === $this->ruleConditionConverter) {
868  $this->ruleConditionConverter = ObjectManager::getInstance()
869  ->get(Converter::class);
870  }
872  }
873 
874  //@codeCoverageIgnoreEnd
875 
879  public function getIdentities()
880  {
881  return ['price'];
882  }
883 }
setDescription($description)
Definition: Rule.php:720
$results
Definition: popup.phtml:13
getData($key='', $index=null)
Definition: DataObject.php:119
validateData(DataObject $dataObject)
Definition: Rule.php:438
_setExtensionAttributes(\Magento\Framework\Api\ExtensionAttributesInterface $extensionAttributes)
$storeManager
setRuleCondition($condition)
Definition: Rule.php:752
__()
Definition: __.php:13
$resource
Definition: bulk.php:12
$price
getConditionsFieldSetId($formName='')
Definition: Rule.php:670
$amount
Definition: order.php:14
setStopRulesProcessing($isStopProcessing)
Definition: Rule.php:771
$formName
Definition: gallery.phtml:11
$value
Definition: gender.phtml:16
setProductsFilter($productIds)
Definition: Rule.php:555
$ruleData
Definition: tax_rule.php:26
__construct(Context $context, Registry $registry, FormFactory $formFactory, TimezoneInterface $localeDate, CollectionFactory $productCollectionFactory, StoreManagerInterface $storeManager, CombineFactory $combineFactory, RuleCollectionFactory $actionCollectionFactory, ProductFactory $productFactory, Iterator $resourceIterator, Session $customerSession, Data $catalogRuleData, TypeListInterface $cacheTypesList, DateTime $dateTime, RuleProductProcessor $ruleProductProcessor, AbstractResource $resource=null, AbstractDb $resourceCollection=null, array $relatedCacheTypes=[], array $data=[], ExtensionAttributesFactory $extensionFactory=null, AttributeValueFactory $customAttributeFactory=null, Json $serializer=null, RuleResourceModel $ruleResourceModel=null, ConditionsToCollectionApplier $conditionsToCollectionApplier=null)
Definition: Rule.php:208
validateDiscount($action, $discount)
Definition: Rule.php:460
setExtensionAttributes(RuleExtensionInterface $extensionAttributes)
Definition: Rule.php:856
calcProductPriceRule(Product $product, $price)
Definition: Rule.php:490
dataDiff($array1, $array2)
Definition: Rule.php:651
_getRulesFromProduct($dateTs, $websiteId, $customerGroupId, $productId)
Definition: Rule.php:543
if(!isset($_GET['name'])) $name
Definition: log.php:14