Collection.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\SalesRule\Model\ResourceModel\Rule;
  7. use Magento\Framework\DB\Select;
  8. use Magento\Framework\Serialize\Serializer\Json;
  9. use Magento\Quote\Model\Quote\Address;
  10. /**
  11. * Sales Rules resource collection model.
  12. *
  13. * @api
  14. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  15. * @since 100.0.2
  16. */
  17. class Collection extends \Magento\Rule\Model\ResourceModel\Rule\Collection\AbstractCollection
  18. {
  19. /**
  20. * Store associated with rule entities information map
  21. *
  22. * @var array
  23. */
  24. protected $_associatedEntitiesMap;
  25. /**
  26. * @var \Magento\SalesRule\Model\ResourceModel\Rule\DateApplier
  27. * @since 100.1.0
  28. */
  29. protected $dateApplier;
  30. /**
  31. * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
  32. */
  33. protected $_date;
  34. /**
  35. * @var Json $serializer
  36. */
  37. private $serializer;
  38. /**
  39. * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
  40. * @param \Psr\Log\LoggerInterface $logger
  41. * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
  42. * @param \Magento\Framework\Event\ManagerInterface $eventManager
  43. * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $date
  44. * @param mixed $connection
  45. * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource
  46. * @param Json $serializer Optional parameter for backward compatibility
  47. */
  48. public function __construct(
  49. \Magento\Framework\Data\Collection\EntityFactory $entityFactory,
  50. \Psr\Log\LoggerInterface $logger,
  51. \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
  52. \Magento\Framework\Event\ManagerInterface $eventManager,
  53. \Magento\Framework\Stdlib\DateTime\TimezoneInterface $date,
  54. \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
  55. \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null,
  56. Json $serializer = null
  57. ) {
  58. parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource);
  59. $this->_date = $date;
  60. $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get(Json::class);
  61. $this->_associatedEntitiesMap = $this->getAssociatedEntitiesMap();
  62. }
  63. /**
  64. * Set resource model and determine field mapping
  65. *
  66. * @return void
  67. */
  68. protected function _construct()
  69. {
  70. $this->_init(\Magento\SalesRule\Model\Rule::class, \Magento\SalesRule\Model\ResourceModel\Rule::class);
  71. $this->_map['fields']['rule_id'] = 'main_table.rule_id';
  72. }
  73. /**
  74. * Map data for associated entities
  75. *
  76. * @param string $entityType
  77. * @param string $objectField
  78. * @throws \Magento\Framework\Exception\LocalizedException
  79. * @return void
  80. * @since 100.1.0
  81. */
  82. protected function mapAssociatedEntities($entityType, $objectField)
  83. {
  84. if (!$this->_items) {
  85. return;
  86. }
  87. $entityInfo = $this->_getAssociatedEntityInfo($entityType);
  88. $ruleIdField = $entityInfo['rule_id_field'];
  89. $entityIds = $this->getColumnValues($ruleIdField);
  90. $select = $this->getConnection()->select()->from(
  91. $this->getTable($entityInfo['associations_table'])
  92. )->where(
  93. $ruleIdField . ' IN (?)',
  94. $entityIds
  95. );
  96. $associatedEntities = $this->getConnection()->fetchAll($select);
  97. array_map(function ($associatedEntity) use ($entityInfo, $ruleIdField, $objectField) {
  98. $item = $this->getItemByColumnValue($ruleIdField, $associatedEntity[$ruleIdField]);
  99. $itemAssociatedValue = $item->getData($objectField) === null ? [] : $item->getData($objectField);
  100. $itemAssociatedValue[] = $associatedEntity[$entityInfo['entity_id_field']];
  101. $item->setData($objectField, $itemAssociatedValue);
  102. }, $associatedEntities);
  103. }
  104. /**
  105. * Add website ids and customer group ids to rules data
  106. *
  107. * @return $this
  108. * @throws \Exception
  109. * @since 100.1.0
  110. */
  111. protected function _afterLoad()
  112. {
  113. $this->mapAssociatedEntities('website', 'website_ids');
  114. $this->mapAssociatedEntities('customer_group', 'customer_group_ids');
  115. $this->setFlag('add_websites_to_result', false);
  116. return parent::_afterLoad();
  117. }
  118. /**
  119. * Filter collection by specified website, customer group, coupon code, date.
  120. * Filter collection to use only active rules.
  121. * Involved sorting by sort_order column.
  122. *
  123. * @param int $websiteId
  124. * @param int $customerGroupId
  125. * @param string $couponCode
  126. * @param string|null $now
  127. * @param Address $address allow extensions to further filter out rules based on quote address
  128. * @use $this->addWebsiteGroupDateFilter()
  129. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  130. * @return $this
  131. */
  132. public function setValidationFilter(
  133. $websiteId,
  134. $customerGroupId,
  135. $couponCode = '',
  136. $now = null,
  137. Address $address = null
  138. ) {
  139. if (!$this->getFlag('validation_filter')) {
  140. /* We need to overwrite joinLeft if coupon is applied */
  141. $this->getSelect()->reset();
  142. parent::_initSelect();
  143. $this->addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now);
  144. $select = $this->getSelect();
  145. $connection = $this->getConnection();
  146. if (strlen($couponCode)) {
  147. $noCouponWhereCondition = $connection->quoteInto(
  148. 'main_table.coupon_type = ?',
  149. \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON
  150. );
  151. $relatedRulesIds = $this->getCouponRelatedRuleIds($couponCode);
  152. $select->where(
  153. $noCouponWhereCondition . ' OR main_table.rule_id IN (?)',
  154. $relatedRulesIds,
  155. Select::TYPE_CONDITION
  156. );
  157. } else {
  158. $this->addFieldToFilter(
  159. 'main_table.coupon_type',
  160. \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON
  161. );
  162. }
  163. $this->setOrder('sort_order', self::SORT_ORDER_ASC);
  164. $this->setFlag('validation_filter', true);
  165. }
  166. return $this;
  167. }
  168. /**
  169. * Get rules ids related to coupon code
  170. *
  171. * @param string $couponCode
  172. * @return array
  173. */
  174. private function getCouponRelatedRuleIds(string $couponCode): array
  175. {
  176. $connection = $this->getConnection();
  177. $select = $connection->select()->from(
  178. ['main_table' => $this->getTable('salesrule')],
  179. 'rule_id'
  180. );
  181. $select->joinLeft(
  182. ['rule_coupons' => $this->getTable('salesrule_coupon')],
  183. $connection->quoteInto(
  184. 'main_table.rule_id = rule_coupons.rule_id AND main_table.coupon_type != ?',
  185. \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON,
  186. null
  187. )
  188. );
  189. $autoGeneratedCouponCondition = [
  190. $connection->quoteInto(
  191. "main_table.coupon_type = ?",
  192. \Magento\SalesRule\Model\Rule::COUPON_TYPE_AUTO
  193. ),
  194. $connection->quoteInto(
  195. "rule_coupons.type = ?",
  196. \Magento\SalesRule\Api\Data\CouponInterface::TYPE_GENERATED
  197. ),
  198. ];
  199. $orWhereConditions = [
  200. "(" . implode($autoGeneratedCouponCondition, " AND ") . ")",
  201. $connection->quoteInto(
  202. '(main_table.coupon_type = ? AND main_table.use_auto_generation = 1 AND rule_coupons.type = 1)',
  203. \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
  204. ),
  205. $connection->quoteInto(
  206. '(main_table.coupon_type = ? AND main_table.use_auto_generation = 0 AND rule_coupons.type = 0)',
  207. \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
  208. ),
  209. ];
  210. $andWhereConditions = [
  211. $connection->quoteInto(
  212. 'rule_coupons.code = ?',
  213. $couponCode
  214. ),
  215. $connection->quoteInto(
  216. '(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)',
  217. $this->_date->date()->format('Y-m-d')
  218. ),
  219. ];
  220. $orWhereCondition = implode(' OR ', $orWhereConditions);
  221. $andWhereCondition = implode(' AND ', $andWhereConditions);
  222. $select->where(
  223. '(' . $orWhereCondition . ') AND ' . $andWhereCondition,
  224. null,
  225. Select::TYPE_CONDITION
  226. );
  227. $select->group('main_table.rule_id');
  228. return $connection->fetchCol($select);
  229. }
  230. /**
  231. * Filter collection by website(s), customer group(s) and date.
  232. * Filter collection to only active rules.
  233. * Sorting is not involved
  234. *
  235. * @param int $websiteId
  236. * @param int $customerGroupId
  237. * @param string|null $now
  238. * @use $this->addWebsiteFilter()
  239. * @return $this
  240. */
  241. public function addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now = null)
  242. {
  243. if (!$this->getFlag('website_group_date_filter')) {
  244. if ($now === null) {
  245. $now = $this->_date->date()->format('Y-m-d');
  246. }
  247. $this->addWebsiteFilter($websiteId);
  248. $entityInfo = $this->_getAssociatedEntityInfo('customer_group');
  249. $connection = $this->getConnection();
  250. $this->getSelect()->joinInner(
  251. ['customer_group_ids' => $this->getTable($entityInfo['associations_table'])],
  252. $connection->quoteInto(
  253. 'main_table.' .
  254. $entityInfo['rule_id_field'] .
  255. ' = customer_group_ids.' .
  256. $entityInfo['rule_id_field'] .
  257. ' AND customer_group_ids.' .
  258. $entityInfo['entity_id_field'] .
  259. ' = ?',
  260. (int)$customerGroupId
  261. ),
  262. []
  263. );
  264. $this->getDateApplier()->applyDate($this->getSelect(), $now);
  265. $this->addIsActiveFilter();
  266. $this->setFlag('website_group_date_filter', true);
  267. }
  268. return $this;
  269. }
  270. /**
  271. * Add primary coupon to collection
  272. *
  273. * @return $this
  274. */
  275. public function _initSelect()
  276. {
  277. parent::_initSelect();
  278. $this->getSelect()->joinLeft(
  279. ['rule_coupons' => $this->getTable('salesrule_coupon')],
  280. 'main_table.rule_id = rule_coupons.rule_id AND rule_coupons.is_primary = 1',
  281. ['code']
  282. );
  283. return $this;
  284. }
  285. /**
  286. * Find product attribute in conditions or actions
  287. *
  288. * @param string $attributeCode
  289. * @return $this
  290. */
  291. public function addAttributeInConditionFilter($attributeCode)
  292. {
  293. $match = sprintf('%%%s%%', substr($this->serializer->serialize(['attribute' => $attributeCode]), 1, -1));
  294. /**
  295. * Information about conditions and actions stored in table as JSON encoded array
  296. * in fields conditions_serialized and actions_serialized.
  297. * If you want to find rules that contains some particular attribute, the easiest way to do so is serialize
  298. * attribute code in the same way as it stored in the serialized columns and execute SQL search
  299. * with like condition.
  300. * Table
  301. * +-------------------------------------------------------------------+
  302. * | conditions_serialized | actions_serialized |
  303. * +-------------------------------------------------------------------+
  304. * | {..."attribute":"attr_name"...} | {..."attribute":"attr_name"...} |
  305. * +---------------------------------|---------------------------------+
  306. * From attribute code "attr_code", will be generated such SQL:
  307. * `condition_serialized` LIKE '%"attribute":"attr_name"%'
  308. * OR `actions_serialized` LIKE '%"attribute":"attr_name"%'
  309. */
  310. $field = $this->_getMappedField('conditions_serialized');
  311. $cCond = $this->_getConditionSql($field, ['like' => $match]);
  312. $field = $this->_getMappedField('actions_serialized');
  313. $aCond = $this->_getConditionSql($field, ['like' => $match]);
  314. $this->getSelect()->where(
  315. sprintf('(%s OR %s)', $cCond, $aCond),
  316. null,
  317. Select::TYPE_CONDITION
  318. );
  319. return $this;
  320. }
  321. /**
  322. * Excludes price rules with generated specific coupon codes from collection
  323. *
  324. * @return $this
  325. */
  326. public function addAllowedSalesRulesFilter()
  327. {
  328. $this->addFieldToFilter('main_table.use_auto_generation', ['neq' => 1]);
  329. return $this;
  330. }
  331. /**
  332. * Limit rules collection by specific customer group
  333. *
  334. * @param int $customerGroupId
  335. * @return $this
  336. * @since 100.1.0
  337. */
  338. public function addCustomerGroupFilter($customerGroupId)
  339. {
  340. $entityInfo = $this->_getAssociatedEntityInfo('customer_group');
  341. if (!$this->getFlag('is_customer_group_joined')) {
  342. $this->setFlag('is_customer_group_joined', true);
  343. $this->getSelect()->join(
  344. ['customer_group' => $this->getTable($entityInfo['associations_table'])],
  345. $this->getConnection()
  346. ->quoteInto('customer_group.' . $entityInfo['entity_id_field'] . ' = ?', $customerGroupId)
  347. . ' AND main_table.' . $entityInfo['rule_id_field'] . ' = customer_group.'
  348. . $entityInfo['rule_id_field'],
  349. []
  350. );
  351. }
  352. return $this;
  353. }
  354. /**
  355. * Getter for _associatedEntitiesMap property
  356. *
  357. * @return array
  358. * @deprecated 100.1.0
  359. */
  360. private function getAssociatedEntitiesMap()
  361. {
  362. if (!$this->_associatedEntitiesMap) {
  363. $this->_associatedEntitiesMap = \Magento\Framework\App\ObjectManager::getInstance()
  364. ->get(\Magento\SalesRule\Model\ResourceModel\Rule\AssociatedEntityMap::class)
  365. ->getData();
  366. }
  367. return $this->_associatedEntitiesMap;
  368. }
  369. /**
  370. * Getter for dateApplier property
  371. *
  372. * @return DateApplier
  373. * @deprecated 100.1.0
  374. */
  375. private function getDateApplier()
  376. {
  377. if (null === $this->dateApplier) {
  378. $this->dateApplier = \Magento\Framework\App\ObjectManager::getInstance()
  379. ->get(\Magento\SalesRule\Model\ResourceModel\Rule\DateApplier::class);
  380. }
  381. return $this->dateApplier;
  382. }
  383. }