Rule.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\SalesRule\Model;
  7. use Magento\Framework\Api\AttributeValueFactory;
  8. use Magento\Framework\Api\ExtensionAttributesFactory;
  9. use Magento\Quote\Model\Quote\Address;
  10. /**
  11. * Shopping Cart Rule data model
  12. *
  13. * @api
  14. * @method string getName()
  15. * @method \Magento\SalesRule\Model\Rule setName(string $value)
  16. * @method string getDescription()
  17. * @method \Magento\SalesRule\Model\Rule setDescription(string $value)
  18. * @method \Magento\SalesRule\Model\Rule setFromDate(string $value)
  19. * @method \Magento\SalesRule\Model\Rule setToDate(string $value)
  20. * @method int getUsesPerCustomer()
  21. * @method \Magento\SalesRule\Model\Rule setUsesPerCustomer(int $value)
  22. * @method int getUsesPerCoupon()
  23. * @method \Magento\SalesRule\Model\Rule setUsesPerCoupon(int $value)
  24. * @method \Magento\SalesRule\Model\Rule setCustomerGroupIds(string $value)
  25. * @method int getIsActive()
  26. * @method \Magento\SalesRule\Model\Rule setIsActive(int $value)
  27. * @method string getConditionsSerialized()
  28. * @method \Magento\SalesRule\Model\Rule setConditionsSerialized(string $value)
  29. * @method string getActionsSerialized()
  30. * @method \Magento\SalesRule\Model\Rule setActionsSerialized(string $value)
  31. * @method int getStopRulesProcessing()
  32. * @method \Magento\SalesRule\Model\Rule setStopRulesProcessing(int $value)
  33. * @method int getIsAdvanced()
  34. * @method \Magento\SalesRule\Model\Rule setIsAdvanced(int $value)
  35. * @method string getProductIds()
  36. * @method \Magento\SalesRule\Model\Rule setProductIds(string $value)
  37. * @method int getSortOrder()
  38. * @method \Magento\SalesRule\Model\Rule setSortOrder(int $value)
  39. * @method string getSimpleAction()
  40. * @method \Magento\SalesRule\Model\Rule setSimpleAction(string $value)
  41. * @method float getDiscountAmount()
  42. * @method \Magento\SalesRule\Model\Rule setDiscountAmount(float $value)
  43. * @method float getDiscountQty()
  44. * @method \Magento\SalesRule\Model\Rule setDiscountQty(float $value)
  45. * @method int getDiscountStep()
  46. * @method \Magento\SalesRule\Model\Rule setDiscountStep(int $value)
  47. * @method int getApplyToShipping()
  48. * @method \Magento\SalesRule\Model\Rule setApplyToShipping(int $value)
  49. * @method int getTimesUsed()
  50. * @method \Magento\SalesRule\Model\Rule setTimesUsed(int $value)
  51. * @method int getIsRss()
  52. * @method \Magento\SalesRule\Model\Rule setIsRss(int $value)
  53. * @method string getWebsiteIds()
  54. * @method \Magento\SalesRule\Model\Rule setWebsiteIds(string $value)
  55. * @method int getCouponType()
  56. * @method \Magento\SalesRule\Model\Rule setCouponType(int $value)
  57. * @method int getUseAutoGeneration()
  58. * @method \Magento\SalesRule\Model\Rule setUseAutoGeneration(int $value)
  59. * @method string getCouponCode()
  60. * @method \Magento\SalesRule\Model\Rule setCouponCode(string $value)
  61. * @method int getRuleId()
  62. * @method \Magento\SalesRule\Model\Rule setRuleId(int $ruleId)
  63. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  64. * @since 100.0.2
  65. */
  66. class Rule extends \Magento\Rule\Model\AbstractModel
  67. {
  68. /**
  69. * Coupon types
  70. */
  71. const COUPON_TYPE_NO_COUPON = 1;
  72. const COUPON_TYPE_SPECIFIC = 2;
  73. const COUPON_TYPE_AUTO = 3;
  74. /**
  75. * Rule type actions
  76. */
  77. const TO_PERCENT_ACTION = 'to_percent';
  78. const BY_PERCENT_ACTION = 'by_percent';
  79. const TO_FIXED_ACTION = 'to_fixed';
  80. const BY_FIXED_ACTION = 'by_fixed';
  81. const CART_FIXED_ACTION = 'cart_fixed';
  82. const BUY_X_GET_Y_ACTION = 'buy_x_get_y';
  83. /**
  84. * Store coupon code generator instance
  85. *
  86. * @var \Magento\SalesRule\Model\Coupon\CodegeneratorInterface
  87. */
  88. protected $_couponCodeGenerator;
  89. /**
  90. * Prefix of model events names
  91. *
  92. * @var string
  93. */
  94. protected $_eventPrefix = 'salesrule_rule';
  95. /**
  96. * Parameter name in event
  97. *
  98. * In observe method you can use $observer->getEvent()->getRule() in this case
  99. *
  100. * @var string
  101. */
  102. protected $_eventObject = 'rule';
  103. /**
  104. * Rule's primary coupon
  105. *
  106. * @var \Magento\SalesRule\Model\Coupon
  107. */
  108. protected $_primaryCoupon;
  109. /**
  110. * Rule's subordinate coupons
  111. *
  112. * @var \Magento\SalesRule\Model\Coupon[]
  113. */
  114. protected $_coupons;
  115. /**
  116. * Coupon types cache for lazy getter
  117. *
  118. * @var array
  119. */
  120. protected $_couponTypes;
  121. /**
  122. * Store already validated addresses and validation results
  123. *
  124. * @var array
  125. */
  126. protected $_validatedAddresses = [];
  127. /**
  128. * @var \Magento\SalesRule\Model\CouponFactory
  129. */
  130. protected $_couponFactory;
  131. /**
  132. * @var \Magento\SalesRule\Model\Coupon\CodegeneratorFactory
  133. */
  134. protected $_codegenFactory;
  135. /**
  136. * @var \Magento\SalesRule\Model\Rule\Condition\CombineFactory
  137. */
  138. protected $_condCombineFactory;
  139. /**
  140. * @var \Magento\SalesRule\Model\Rule\Condition\Product\CombineFactory
  141. */
  142. protected $_condProdCombineF;
  143. /**
  144. * @var \Magento\SalesRule\Model\ResourceModel\Coupon\Collection
  145. */
  146. protected $_couponCollection;
  147. /**
  148. * @var \Magento\Store\Model\StoreManagerInterface
  149. */
  150. protected $_storeManager;
  151. /**
  152. * Rule constructor
  153. *
  154. * @param \Magento\Framework\Model\Context $context
  155. * @param \Magento\Framework\Registry $registry
  156. * @param \Magento\Framework\Data\FormFactory $formFactory
  157. * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
  158. * @param CouponFactory $couponFactory
  159. * @param Coupon\CodegeneratorFactory $codegenFactory
  160. * @param Rule\Condition\CombineFactory $condCombineFactory
  161. * @param Rule\Condition\Product\CombineFactory $condProdCombineF
  162. * @param ResourceModel\Coupon\Collection $couponCollection
  163. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  164. * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
  165. * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
  166. * @param array $data
  167. * @param ExtensionAttributesFactory|null $extensionFactory
  168. * @param AttributeValueFactory|null $customAttributeFactory
  169. * @param \Magento\Framework\Serialize\Serializer\Json $serializer
  170. *
  171. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  172. */
  173. public function __construct(
  174. \Magento\Framework\Model\Context $context,
  175. \Magento\Framework\Registry $registry,
  176. \Magento\Framework\Data\FormFactory $formFactory,
  177. \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
  178. \Magento\SalesRule\Model\CouponFactory $couponFactory,
  179. \Magento\SalesRule\Model\Coupon\CodegeneratorFactory $codegenFactory,
  180. \Magento\SalesRule\Model\Rule\Condition\CombineFactory $condCombineFactory,
  181. \Magento\SalesRule\Model\Rule\Condition\Product\CombineFactory $condProdCombineF,
  182. \Magento\SalesRule\Model\ResourceModel\Coupon\Collection $couponCollection,
  183. \Magento\Store\Model\StoreManagerInterface $storeManager,
  184. \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
  185. \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
  186. array $data = [],
  187. ExtensionAttributesFactory $extensionFactory = null,
  188. AttributeValueFactory $customAttributeFactory = null,
  189. \Magento\Framework\Serialize\Serializer\Json $serializer = null
  190. ) {
  191. $this->_couponFactory = $couponFactory;
  192. $this->_codegenFactory = $codegenFactory;
  193. $this->_condCombineFactory = $condCombineFactory;
  194. $this->_condProdCombineF = $condProdCombineF;
  195. $this->_couponCollection = $couponCollection;
  196. $this->_storeManager = $storeManager;
  197. parent::__construct(
  198. $context,
  199. $registry,
  200. $formFactory,
  201. $localeDate,
  202. $resource,
  203. $resourceCollection,
  204. $data,
  205. $extensionFactory,
  206. $customAttributeFactory,
  207. $serializer
  208. );
  209. }
  210. /**
  211. * Set resource model and Id field name
  212. *
  213. * @return void
  214. */
  215. protected function _construct()
  216. {
  217. parent::_construct();
  218. $this->_init(\Magento\SalesRule\Model\ResourceModel\Rule::class);
  219. $this->setIdFieldName('rule_id');
  220. }
  221. /**
  222. * Set coupon code and uses per coupon
  223. *
  224. * @return $this
  225. */
  226. protected function _afterLoad()
  227. {
  228. $this->loadRelations();
  229. return parent::_afterLoad();
  230. }
  231. /**
  232. * Load all relative data
  233. *
  234. * @return void
  235. */
  236. public function loadRelations()
  237. {
  238. $this->loadCouponCode();
  239. }
  240. /**
  241. * Load coupon code
  242. *
  243. * @return void
  244. */
  245. public function loadCouponCode()
  246. {
  247. $this->setCouponCode($this->getPrimaryCoupon()->getCode());
  248. if ($this->getUsesPerCoupon() == null && !$this->getUseAutoGeneration()) {
  249. $this->setUsesPerCoupon($this->getPrimaryCoupon()->getUsageLimit());
  250. }
  251. }
  252. /**
  253. * Save/delete coupon
  254. *
  255. * @return $this
  256. */
  257. public function afterSave()
  258. {
  259. $couponCode = trim($this->getCouponCode());
  260. if (strlen(
  261. $couponCode
  262. ) && $this->getCouponType() == self::COUPON_TYPE_SPECIFIC && !$this->getUseAutoGeneration()
  263. ) {
  264. $this->getPrimaryCoupon()->setCode(
  265. $couponCode
  266. )->setUsageLimit(
  267. $this->getUsesPerCoupon() ? $this->getUsesPerCoupon() : null
  268. )->setUsagePerCustomer(
  269. $this->getUsesPerCustomer() ? $this->getUsesPerCustomer() : null
  270. )->setExpirationDate(
  271. $this->getToDate()
  272. )->save();
  273. } else {
  274. $this->getPrimaryCoupon()->delete();
  275. }
  276. parent::afterSave();
  277. return $this;
  278. }
  279. /**
  280. * Initialize rule model data from array. Set store labels if applicable.
  281. *
  282. * @param array $data
  283. * @return $this
  284. */
  285. public function loadPost(array $data)
  286. {
  287. parent::loadPost($data);
  288. if (isset($data['store_labels'])) {
  289. $this->setStoreLabels($data['store_labels']);
  290. }
  291. return $this;
  292. }
  293. /**
  294. * Get rule condition combine model instance
  295. *
  296. * @return \Magento\SalesRule\Model\Rule\Condition\Combine
  297. */
  298. public function getConditionsInstance()
  299. {
  300. return $this->_condCombineFactory->create();
  301. }
  302. /**
  303. * Get rule condition product combine model instance
  304. *
  305. * @return \Magento\SalesRule\Model\Rule\Condition\Product\Combine
  306. */
  307. public function getActionsInstance()
  308. {
  309. return $this->_condProdCombineF->create();
  310. }
  311. /**
  312. * Returns code generator instance for auto generated coupons
  313. *
  314. * @return \Magento\SalesRule\Model\Coupon\CodegeneratorInterface
  315. */
  316. public function getCouponCodeGenerator()
  317. {
  318. if (!$this->_couponCodeGenerator) {
  319. return $this->_codegenFactory->create(['data' => ['length' => 16]]);
  320. }
  321. return $this->_couponCodeGenerator;
  322. }
  323. /**
  324. * Set code generator instance for auto generated coupons
  325. *
  326. * @param \Magento\SalesRule\Model\Coupon\CodegeneratorInterface $codeGenerator
  327. * @return void
  328. */
  329. public function setCouponCodeGenerator(\Magento\SalesRule\Model\Coupon\CodegeneratorInterface $codeGenerator)
  330. {
  331. $this->_couponCodeGenerator = $codeGenerator;
  332. }
  333. /**
  334. * Retrieve rule's primary coupon
  335. *
  336. * @return \Magento\SalesRule\Model\Coupon
  337. */
  338. public function getPrimaryCoupon()
  339. {
  340. if ($this->_primaryCoupon === null) {
  341. $this->_primaryCoupon = $this->_couponFactory->create();
  342. $this->_primaryCoupon->loadPrimaryByRule($this->getId());
  343. $this->_primaryCoupon->setRule($this)->setIsPrimary(true);
  344. }
  345. return $this->_primaryCoupon;
  346. }
  347. /**
  348. * Get sales rule customer group Ids
  349. *
  350. * @return array
  351. */
  352. public function getCustomerGroupIds()
  353. {
  354. if (!$this->hasCustomerGroupIds()) {
  355. $customerGroupIds = $this->_getResource()->getCustomerGroupIds($this->getId());
  356. $this->setData('customer_group_ids', (array)$customerGroupIds);
  357. }
  358. return $this->_getData('customer_group_ids');
  359. }
  360. /**
  361. * Get Rule label by specified store
  362. *
  363. * @param \Magento\Store\Model\Store|int|bool|null $store
  364. * @return string|bool
  365. */
  366. public function getStoreLabel($store = null)
  367. {
  368. $storeId = $this->_storeManager->getStore($store)->getId();
  369. $labels = (array)$this->getStoreLabels();
  370. if (isset($labels[$storeId])) {
  371. return $labels[$storeId];
  372. } elseif (isset($labels[0]) && $labels[0]) {
  373. return $labels[0];
  374. }
  375. return false;
  376. }
  377. /**
  378. * Set if not yet and retrieve rule store labels
  379. *
  380. * @return array
  381. */
  382. public function getStoreLabels()
  383. {
  384. if (!$this->hasStoreLabels()) {
  385. $labels = $this->_getResource()->getStoreLabels($this->getId());
  386. $this->setStoreLabels($labels);
  387. }
  388. return $this->_getData('store_labels');
  389. }
  390. /**
  391. * Retrieve subordinate coupons
  392. *
  393. * @return \Magento\SalesRule\Model\Coupon[]
  394. */
  395. public function getCoupons()
  396. {
  397. if ($this->_coupons === null) {
  398. $this->_couponCollection->addRuleToFilter($this);
  399. $this->_coupons = $this->_couponCollection->getItems();
  400. }
  401. return $this->_coupons;
  402. }
  403. /**
  404. * Retrieve coupon types
  405. *
  406. * @return array
  407. */
  408. public function getCouponTypes()
  409. {
  410. if ($this->_couponTypes === null) {
  411. $this->_couponTypes = [
  412. \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON => __('No Coupon'),
  413. \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC => __('Specific Coupon'),
  414. ];
  415. $transport = new \Magento\Framework\DataObject(
  416. ['coupon_types' => $this->_couponTypes, 'is_coupon_type_auto_visible' => false]
  417. );
  418. $this->_eventManager->dispatch('salesrule_rule_get_coupon_types', ['transport' => $transport]);
  419. $this->_couponTypes = $transport->getCouponTypes();
  420. if ($transport->getIsCouponTypeAutoVisible()) {
  421. $this->_couponTypes[\Magento\SalesRule\Model\Rule::COUPON_TYPE_AUTO] = __('Auto');
  422. }
  423. }
  424. return $this->_couponTypes;
  425. }
  426. /**
  427. * Acquire coupon instance
  428. *
  429. * @param bool $saveNewlyCreated Whether or not to save newly created coupon
  430. * @param int $saveAttemptCount Number of attempts to save newly created coupon
  431. * @return \Magento\SalesRule\Model\Coupon|null
  432. * @throws \Exception|\Magento\Framework\Exception\LocalizedException
  433. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  434. * @SuppressWarnings(PHPMD.NPathComplexity)
  435. */
  436. public function acquireCoupon($saveNewlyCreated = true, $saveAttemptCount = 10)
  437. {
  438. if ($this->getCouponType() == self::COUPON_TYPE_NO_COUPON) {
  439. return null;
  440. }
  441. if ($this->getCouponType() == self::COUPON_TYPE_SPECIFIC) {
  442. return $this->getPrimaryCoupon();
  443. }
  444. /** @var \Magento\SalesRule\Model\Coupon $coupon */
  445. $coupon = $this->_couponFactory->create();
  446. $coupon->setRule(
  447. $this
  448. )->setIsPrimary(
  449. false
  450. )->setUsageLimit(
  451. $this->getUsesPerCoupon() ? $this->getUsesPerCoupon() : null
  452. )->setUsagePerCustomer(
  453. $this->getUsesPerCustomer() ? $this->getUsesPerCustomer() : null
  454. )->setExpirationDate(
  455. $this->getToDate()
  456. )->setType(
  457. \Magento\SalesRule\Api\Data\CouponInterface::TYPE_GENERATED
  458. );
  459. $couponCode = self::getCouponCodeGenerator()->generateCode();
  460. $coupon->setCode($couponCode);
  461. $ok = false;
  462. if (!$saveNewlyCreated) {
  463. $ok = true;
  464. } else {
  465. if ($this->getId()) {
  466. for ($attemptNum = 0; $attemptNum < $saveAttemptCount; $attemptNum++) {
  467. try {
  468. $coupon->save();
  469. } catch (\Exception $e) {
  470. if ($e instanceof \Magento\Framework\Exception\LocalizedException || $coupon->getId()) {
  471. throw $e;
  472. }
  473. $coupon->setCode(
  474. $couponCode . self::getCouponCodeGenerator()->getDelimiter() . sprintf(
  475. '%04u',
  476. random_int(0, 9999)
  477. )
  478. );
  479. continue;
  480. }
  481. $ok = true;
  482. break;
  483. }
  484. }
  485. }
  486. if (!$ok) {
  487. throw new \Magento\Framework\Exception\LocalizedException(__('Can\'t acquire coupon.'));
  488. }
  489. return $coupon;
  490. }
  491. /**
  492. * Get from date.
  493. *
  494. * @return string
  495. * @since 100.1.0
  496. */
  497. public function getFromDate()
  498. {
  499. return $this->getData('from_date');
  500. }
  501. /**
  502. * Get to date.
  503. *
  504. * @return string
  505. * @since 100.1.0
  506. */
  507. public function getToDate()
  508. {
  509. return $this->getData('to_date');
  510. }
  511. /**
  512. * Check cached validation result for specific address
  513. *
  514. * @param Address $address
  515. * @return bool
  516. */
  517. public function hasIsValidForAddress($address)
  518. {
  519. $addressId = $this->_getAddressId($address);
  520. return isset($this->_validatedAddresses[$addressId]) ? true : false;
  521. }
  522. /**
  523. * Set validation result for specific address to results cache
  524. *
  525. * @param Address $address
  526. * @param bool $validationResult
  527. * @return $this
  528. */
  529. public function setIsValidForAddress($address, $validationResult)
  530. {
  531. $addressId = $this->_getAddressId($address);
  532. $this->_validatedAddresses[$addressId] = $validationResult;
  533. return $this;
  534. }
  535. /**
  536. * Get cached validation result for specific address
  537. *
  538. * @param Address $address
  539. * @return bool
  540. * @SuppressWarnings(PHPMD.BooleanGetMethodName)
  541. */
  542. public function getIsValidForAddress($address)
  543. {
  544. $addressId = $this->_getAddressId($address);
  545. return isset($this->_validatedAddresses[$addressId]) ? $this->_validatedAddresses[$addressId] : false;
  546. }
  547. /**
  548. * Return id for address
  549. *
  550. * @param Address $address
  551. * @return string
  552. */
  553. private function _getAddressId($address)
  554. {
  555. if ($address instanceof Address) {
  556. return $address->getId();
  557. }
  558. return $address;
  559. }
  560. /**
  561. * Get conditions field set id.
  562. *
  563. * @param string $formName
  564. * @return string
  565. * @since 100.1.0
  566. */
  567. public function getConditionsFieldSetId($formName = '')
  568. {
  569. return $formName . 'rule_conditions_fieldset_' . $this->getId();
  570. }
  571. /**
  572. * Get actions field set id.
  573. *
  574. * @param string $formName
  575. * @return string
  576. * @since 100.1.0
  577. */
  578. public function getActionsFieldSetId($formName = '')
  579. {
  580. return $formName . 'rule_actions_fieldset_' . $this->getId();
  581. }
  582. }