Rule.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\SalesRule\Model\ResourceModel;
  7. use Magento\Framework\App\ObjectManager;
  8. use Magento\Framework\EntityManager\EntityManager;
  9. use Magento\Framework\EntityManager\MetadataPool;
  10. use Magento\Framework\Model\AbstractModel;
  11. use Magento\Framework\Serialize\Serializer\Json;
  12. use Magento\Rule\Model\ResourceModel\AbstractResource;
  13. use Magento\SalesRule\Api\Data\RuleInterface;
  14. /**
  15. * Sales Rule resource model
  16. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  17. */
  18. class Rule extends AbstractResource
  19. {
  20. /**
  21. * Store associated with rule entities information map
  22. *
  23. * @var array
  24. */
  25. protected $_associatedEntitiesMap = [];
  26. /**
  27. * @var array
  28. */
  29. protected $customerGroupIds = [];
  30. /**
  31. * @var array
  32. */
  33. protected $websiteIds = [];
  34. /**
  35. * Magento string lib
  36. *
  37. * @var \Magento\Framework\Stdlib\StringUtils
  38. */
  39. protected $string;
  40. /**
  41. * @var \Magento\SalesRule\Model\ResourceModel\Coupon
  42. */
  43. protected $_resourceCoupon;
  44. /**
  45. * @var EntityManager
  46. */
  47. protected $entityManager;
  48. /**
  49. * @var MetadataPool
  50. */
  51. private $metadataPool;
  52. /**
  53. * @param \Magento\Framework\Model\ResourceModel\Db\Context $context
  54. * @param \Magento\Framework\Stdlib\StringUtils $string
  55. * @param \Magento\SalesRule\Model\ResourceModel\Coupon $resourceCoupon
  56. * @param string $connectionName
  57. * @param \Magento\Framework\DataObject|null $associatedEntityMapInstance
  58. * @param Json $serializer Optional parameter for backward compatibility
  59. * @param MetadataPool $metadataPool Optional parameter for backward compatibility
  60. */
  61. public function __construct(
  62. \Magento\Framework\Model\ResourceModel\Db\Context $context,
  63. \Magento\Framework\Stdlib\StringUtils $string,
  64. \Magento\SalesRule\Model\ResourceModel\Coupon $resourceCoupon,
  65. $connectionName = null,
  66. \Magento\Framework\DataObject $associatedEntityMapInstance = null,
  67. Json $serializer = null,
  68. MetadataPool $metadataPool = null
  69. ) {
  70. $this->string = $string;
  71. $this->_resourceCoupon = $resourceCoupon;
  72. $associatedEntitiesMapInstance = $associatedEntityMapInstance ?: ObjectManager::getInstance()->get(
  73. \Magento\SalesRule\Model\ResourceModel\Rule\AssociatedEntityMap::class
  74. );
  75. $this->_associatedEntitiesMap = $associatedEntitiesMapInstance->getData();
  76. $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class);
  77. $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class);
  78. parent::__construct($context, $connectionName);
  79. }
  80. /**
  81. * Initialize main table and table id field
  82. *
  83. * @return void
  84. */
  85. protected function _construct()
  86. {
  87. $this->_init('salesrule', 'rule_id');
  88. }
  89. /**
  90. * @param AbstractModel $object
  91. * @return void
  92. * @deprecated 100.1.0
  93. */
  94. public function loadCustomerGroupIds(AbstractModel $object)
  95. {
  96. if (!$this->customerGroupIds) {
  97. $this->customerGroupIds = (array)$this->getCustomerGroupIds($object->getId());
  98. }
  99. $object->setData('customer_group_ids', $this->customerGroupIds);
  100. }
  101. /**
  102. * @param AbstractModel $object
  103. * @return void
  104. * @deprecated 100.1.0
  105. */
  106. public function loadWebsiteIds(AbstractModel $object)
  107. {
  108. if (!$this->websiteIds) {
  109. $this->websiteIds = (array)$this->getWebsiteIds($object->getId());
  110. }
  111. $object->setData('website_ids', $this->websiteIds);
  112. }
  113. /**
  114. * Prepare sales rule's discount quantity
  115. *
  116. * @param \Magento\Framework\Model\AbstractModel $object
  117. * @return $this
  118. */
  119. public function _beforeSave(AbstractModel $object)
  120. {
  121. if (!$object->getDiscountQty()) {
  122. $object->setDiscountQty(new \Zend_Db_Expr('NULL'));
  123. }
  124. parent::_beforeSave($object);
  125. return $this;
  126. }
  127. /**
  128. * Load an object
  129. *
  130. * @param AbstractModel $object
  131. * @param mixed $value
  132. * @param string $field field to load by (defaults to model id)
  133. * @return $this
  134. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  135. */
  136. public function load(AbstractModel $object, $value, $field = null)
  137. {
  138. $this->getEntityManager()->load($object, $value);
  139. return $this;
  140. }
  141. /**
  142. * Bind sales rule to customer group(s) and website(s).
  143. * Save rule's associated store labels.
  144. * Save product attributes used in rule.
  145. *
  146. * @param \Magento\Framework\Model\AbstractModel $object
  147. * @return $this
  148. */
  149. protected function _afterSave(AbstractModel $object)
  150. {
  151. if ($object->hasStoreLabels()) {
  152. $this->saveStoreLabels($object->getId(), $object->getStoreLabels());
  153. }
  154. // Save product attributes used in rule
  155. $ruleProductAttributes = array_merge(
  156. $this->getProductAttributes($this->serializer->serialize($object->getConditions()->asArray())),
  157. $this->getProductAttributes($this->serializer->serialize($object->getActions()->asArray()))
  158. );
  159. if (count($ruleProductAttributes)) {
  160. $this->setActualProductAttributes($object, $ruleProductAttributes);
  161. }
  162. // Update auto geterated specific coupons if exists
  163. if ($object->getUseAutoGeneration() && $object->hasDataChanges()) {
  164. $this->_resourceCoupon->updateSpecificCoupons($object);
  165. }
  166. return parent::_afterSave($object);
  167. }
  168. /**
  169. * Retrieve coupon/rule uses for specified customer
  170. *
  171. * @param \Magento\SalesRule\Model\Rule $rule
  172. * @param int $customerId
  173. * @return string
  174. */
  175. public function getCustomerUses($rule, $customerId)
  176. {
  177. $connection = $this->getConnection();
  178. $select = $connection->select()->from(
  179. $this->getTable('salesrule_customer'),
  180. ['cnt' => 'count(*)']
  181. )->where(
  182. 'rule_id = :rule_id'
  183. )->where(
  184. 'customer_id = :customer_id'
  185. );
  186. return $connection->fetchOne($select, [':rule_id' => $rule->getRuleId(), ':customer_id' => $customerId]);
  187. }
  188. /**
  189. * Save rule labels for different store views
  190. *
  191. * @param int $ruleId
  192. * @param array $labels
  193. * @throws \Exception
  194. * @return $this
  195. */
  196. public function saveStoreLabels($ruleId, $labels)
  197. {
  198. $deleteByStoreIds = [];
  199. $table = $this->getTable('salesrule_label');
  200. $connection = $this->getConnection();
  201. $data = [];
  202. foreach ($labels as $storeId => $label) {
  203. if ($this->string->strlen($label)) {
  204. $data[] = ['rule_id' => $ruleId, 'store_id' => $storeId, 'label' => $label];
  205. } else {
  206. $deleteByStoreIds[] = $storeId;
  207. }
  208. }
  209. $connection->beginTransaction();
  210. try {
  211. if (!empty($data)) {
  212. $connection->insertOnDuplicate($table, $data, ['label']);
  213. }
  214. if (!empty($deleteByStoreIds)) {
  215. $connection->delete($table, ['rule_id=?' => $ruleId, 'store_id IN (?)' => $deleteByStoreIds]);
  216. }
  217. } catch (\Exception $e) {
  218. $connection->rollBack();
  219. throw $e;
  220. }
  221. $connection->commit();
  222. return $this;
  223. }
  224. /**
  225. * Get all existing rule labels
  226. *
  227. * @param int $ruleId
  228. * @return array
  229. */
  230. public function getStoreLabels($ruleId)
  231. {
  232. $select = $this->getConnection()->select()->from(
  233. $this->getTable('salesrule_label'),
  234. ['store_id', 'label']
  235. )->where(
  236. 'rule_id = :rule_id'
  237. );
  238. return $this->getConnection()->fetchPairs($select, [':rule_id' => $ruleId]);
  239. }
  240. /**
  241. * Get rule label by specific store id
  242. *
  243. * @param int $ruleId
  244. * @param int $storeId
  245. * @return string
  246. */
  247. public function getStoreLabel($ruleId, $storeId)
  248. {
  249. $select = $this->getConnection()->select()->from(
  250. $this->getTable('salesrule_label'),
  251. 'label'
  252. )->where(
  253. 'rule_id = :rule_id'
  254. )->where(
  255. 'store_id IN(0, :store_id)'
  256. )->order(
  257. 'store_id DESC'
  258. );
  259. return $this->getConnection()->fetchOne($select, [':rule_id' => $ruleId, ':store_id' => $storeId]);
  260. }
  261. /**
  262. * Return codes of all product attributes currently used in promo rules
  263. *
  264. * @return array
  265. */
  266. public function getActiveAttributes()
  267. {
  268. $connection = $this->getConnection();
  269. $select = $connection->select()->from(
  270. ['a' => $this->getTable('salesrule_product_attribute')],
  271. new \Zend_Db_Expr('DISTINCT ea.attribute_code')
  272. )->joinInner(
  273. ['ea' => $this->getTable('eav_attribute')],
  274. 'ea.attribute_id = a.attribute_id',
  275. []
  276. );
  277. return $connection->fetchAll($select);
  278. }
  279. /**
  280. * Save product attributes currently used in conditions and actions of rule
  281. *
  282. * @param \Magento\SalesRule\Model\Rule $rule
  283. * @param mixed $attributes
  284. * @return $this
  285. */
  286. public function setActualProductAttributes($rule, $attributes)
  287. {
  288. $connection = $this->getConnection();
  289. $metadata = $this->metadataPool->getMetadata(RuleInterface::class);
  290. $connection->delete(
  291. $this->getTable('salesrule_product_attribute'),
  292. [$metadata->getLinkField() . '=?' => $rule->getData($metadata->getLinkField())]
  293. );
  294. //Getting attribute IDs for attribute codes
  295. $attributeIds = [];
  296. $select = $this->getConnection()->select()->from(
  297. ['a' => $this->getTable('eav_attribute')],
  298. ['a.attribute_id']
  299. )->where(
  300. 'a.attribute_code IN (?)',
  301. [$attributes]
  302. );
  303. $attributesFound = $this->getConnection()->fetchAll($select);
  304. if ($attributesFound) {
  305. foreach ($attributesFound as $attribute) {
  306. $attributeIds[] = $attribute['attribute_id'];
  307. }
  308. $data = [];
  309. foreach ($rule->getCustomerGroupIds() as $customerGroupId) {
  310. foreach ($rule->getWebsiteIds() as $websiteId) {
  311. foreach ($attributeIds as $attribute) {
  312. $data[] = [
  313. $metadata->getLinkField() => $rule->getData($metadata->getLinkField()),
  314. 'website_id' => $websiteId,
  315. 'customer_group_id' => $customerGroupId,
  316. 'attribute_id' => $attribute,
  317. ];
  318. }
  319. }
  320. }
  321. $connection->insertMultiple($this->getTable('salesrule_product_attribute'), $data);
  322. }
  323. return $this;
  324. }
  325. /**
  326. * Collect all product attributes used in serialized rule's action or condition
  327. *
  328. * @param string $serializedString
  329. * @return array
  330. */
  331. public function getProductAttributes($serializedString)
  332. {
  333. // we need 4 backslashes to match 1 in regexp, see http://www.php.net/manual/en/regexp.reference.escape.php
  334. preg_match_all(
  335. '~"Magento\\\\\\\\SalesRule\\\\\\\\Model\\\\\\\\Rule\\\\\\\\Condition\\\\\\\\Product","attribute":"(.*?)"~',
  336. $serializedString,
  337. $matches
  338. );
  339. // we always have $matches like [[],[]]
  340. return array_values($matches[1]);
  341. }
  342. /**
  343. * @param \Magento\Framework\Model\AbstractModel $object
  344. * @return $this
  345. */
  346. public function save(\Magento\Framework\Model\AbstractModel $object)
  347. {
  348. $this->getEntityManager()->save($object);
  349. return $this;
  350. }
  351. /**
  352. * Delete the object
  353. *
  354. * @param \Magento\Framework\Model\AbstractModel $object
  355. * @return $this
  356. */
  357. public function delete(AbstractModel $object)
  358. {
  359. $this->getEntityManager()->delete($object);
  360. return $this;
  361. }
  362. /**
  363. * @return \Magento\Framework\EntityManager\EntityManager
  364. * @deprecated 100.1.0
  365. */
  366. private function getEntityManager()
  367. {
  368. if (null === $this->entityManager) {
  369. $this->entityManager = \Magento\Framework\App\ObjectManager::getInstance()
  370. ->get(\Magento\Framework\EntityManager\EntityManager::class);
  371. }
  372. return $this->entityManager;
  373. }
  374. }