<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\SalesRule\Model\ResourceModel\Rule; use Magento\Framework\DB\Select; use Magento\Framework\Serialize\Serializer\Json; use Magento\Quote\Model\Quote\Address; /** * Sales Rules resource collection model. * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 */ class Collection extends \Magento\Rule\Model\ResourceModel\Rule\Collection\AbstractCollection { /** * Store associated with rule entities information map * * @var array */ protected $_associatedEntitiesMap; /** * @var \Magento\SalesRule\Model\ResourceModel\Rule\DateApplier * @since 100.1.0 */ protected $dateApplier; /** * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface */ protected $_date; /** * @var Json $serializer */ private $serializer; /** * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $date * @param mixed $connection * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource * @param Json $serializer Optional parameter for backward compatibility */ public function __construct( \Magento\Framework\Data\Collection\EntityFactory $entityFactory, \Psr\Log\LoggerInterface $logger, \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy, \Magento\Framework\Event\ManagerInterface $eventManager, \Magento\Framework\Stdlib\DateTime\TimezoneInterface $date, \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null, Json $serializer = null ) { parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource); $this->_date = $date; $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get(Json::class); $this->_associatedEntitiesMap = $this->getAssociatedEntitiesMap(); } /** * Set resource model and determine field mapping * * @return void */ protected function _construct() { $this->_init(\Magento\SalesRule\Model\Rule::class, \Magento\SalesRule\Model\ResourceModel\Rule::class); $this->_map['fields']['rule_id'] = 'main_table.rule_id'; } /** * Map data for associated entities * * @param string $entityType * @param string $objectField * @throws \Magento\Framework\Exception\LocalizedException * @return void * @since 100.1.0 */ protected function mapAssociatedEntities($entityType, $objectField) { if (!$this->_items) { return; } $entityInfo = $this->_getAssociatedEntityInfo($entityType); $ruleIdField = $entityInfo['rule_id_field']; $entityIds = $this->getColumnValues($ruleIdField); $select = $this->getConnection()->select()->from( $this->getTable($entityInfo['associations_table']) )->where( $ruleIdField . ' IN (?)', $entityIds ); $associatedEntities = $this->getConnection()->fetchAll($select); array_map(function ($associatedEntity) use ($entityInfo, $ruleIdField, $objectField) { $item = $this->getItemByColumnValue($ruleIdField, $associatedEntity[$ruleIdField]); $itemAssociatedValue = $item->getData($objectField) === null ? [] : $item->getData($objectField); $itemAssociatedValue[] = $associatedEntity[$entityInfo['entity_id_field']]; $item->setData($objectField, $itemAssociatedValue); }, $associatedEntities); } /** * Add website ids and customer group ids to rules data * * @return $this * @throws \Exception * @since 100.1.0 */ protected function _afterLoad() { $this->mapAssociatedEntities('website', 'website_ids'); $this->mapAssociatedEntities('customer_group', 'customer_group_ids'); $this->setFlag('add_websites_to_result', false); return parent::_afterLoad(); } /** * Filter collection by specified website, customer group, coupon code, date. * Filter collection to use only active rules. * Involved sorting by sort_order column. * * @param int $websiteId * @param int $customerGroupId * @param string $couponCode * @param string|null $now * @param Address $address allow extensions to further filter out rules based on quote address * @use $this->addWebsiteGroupDateFilter() * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @return $this */ public function setValidationFilter( $websiteId, $customerGroupId, $couponCode = '', $now = null, Address $address = null ) { if (!$this->getFlag('validation_filter')) { /* We need to overwrite joinLeft if coupon is applied */ $this->getSelect()->reset(); parent::_initSelect(); $this->addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now); $select = $this->getSelect(); $connection = $this->getConnection(); if (strlen($couponCode)) { $noCouponWhereCondition = $connection->quoteInto( 'main_table.coupon_type = ?', \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON ); $relatedRulesIds = $this->getCouponRelatedRuleIds($couponCode); $select->where( $noCouponWhereCondition . ' OR main_table.rule_id IN (?)', $relatedRulesIds, Select::TYPE_CONDITION ); } else { $this->addFieldToFilter( 'main_table.coupon_type', \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON ); } $this->setOrder('sort_order', self::SORT_ORDER_ASC); $this->setFlag('validation_filter', true); } return $this; } /** * Get rules ids related to coupon code * * @param string $couponCode * @return array */ private function getCouponRelatedRuleIds(string $couponCode): array { $connection = $this->getConnection(); $select = $connection->select()->from( ['main_table' => $this->getTable('salesrule')], 'rule_id' ); $select->joinLeft( ['rule_coupons' => $this->getTable('salesrule_coupon')], $connection->quoteInto( 'main_table.rule_id = rule_coupons.rule_id AND main_table.coupon_type != ?', \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON, null ) ); $autoGeneratedCouponCondition = [ $connection->quoteInto( "main_table.coupon_type = ?", \Magento\SalesRule\Model\Rule::COUPON_TYPE_AUTO ), $connection->quoteInto( "rule_coupons.type = ?", \Magento\SalesRule\Api\Data\CouponInterface::TYPE_GENERATED ), ]; $orWhereConditions = [ "(" . implode($autoGeneratedCouponCondition, " AND ") . ")", $connection->quoteInto( '(main_table.coupon_type = ? AND main_table.use_auto_generation = 1 AND rule_coupons.type = 1)', \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC ), $connection->quoteInto( '(main_table.coupon_type = ? AND main_table.use_auto_generation = 0 AND rule_coupons.type = 0)', \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC ), ]; $andWhereConditions = [ $connection->quoteInto( 'rule_coupons.code = ?', $couponCode ), $connection->quoteInto( '(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)', $this->_date->date()->format('Y-m-d') ), ]; $orWhereCondition = implode(' OR ', $orWhereConditions); $andWhereCondition = implode(' AND ', $andWhereConditions); $select->where( '(' . $orWhereCondition . ') AND ' . $andWhereCondition, null, Select::TYPE_CONDITION ); $select->group('main_table.rule_id'); return $connection->fetchCol($select); } /** * Filter collection by website(s), customer group(s) and date. * Filter collection to only active rules. * Sorting is not involved * * @param int $websiteId * @param int $customerGroupId * @param string|null $now * @use $this->addWebsiteFilter() * @return $this */ public function addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now = null) { if (!$this->getFlag('website_group_date_filter')) { if ($now === null) { $now = $this->_date->date()->format('Y-m-d'); } $this->addWebsiteFilter($websiteId); $entityInfo = $this->_getAssociatedEntityInfo('customer_group'); $connection = $this->getConnection(); $this->getSelect()->joinInner( ['customer_group_ids' => $this->getTable($entityInfo['associations_table'])], $connection->quoteInto( 'main_table.' . $entityInfo['rule_id_field'] . ' = customer_group_ids.' . $entityInfo['rule_id_field'] . ' AND customer_group_ids.' . $entityInfo['entity_id_field'] . ' = ?', (int)$customerGroupId ), [] ); $this->getDateApplier()->applyDate($this->getSelect(), $now); $this->addIsActiveFilter(); $this->setFlag('website_group_date_filter', true); } return $this; } /** * Add primary coupon to collection * * @return $this */ public function _initSelect() { parent::_initSelect(); $this->getSelect()->joinLeft( ['rule_coupons' => $this->getTable('salesrule_coupon')], 'main_table.rule_id = rule_coupons.rule_id AND rule_coupons.is_primary = 1', ['code'] ); return $this; } /** * Find product attribute in conditions or actions * * @param string $attributeCode * @return $this */ public function addAttributeInConditionFilter($attributeCode) { $match = sprintf('%%%s%%', substr($this->serializer->serialize(['attribute' => $attributeCode]), 1, -1)); /** * Information about conditions and actions stored in table as JSON encoded array * in fields conditions_serialized and actions_serialized. * If you want to find rules that contains some particular attribute, the easiest way to do so is serialize * attribute code in the same way as it stored in the serialized columns and execute SQL search * with like condition. * Table * +-------------------------------------------------------------------+ * | conditions_serialized | actions_serialized | * +-------------------------------------------------------------------+ * | {..."attribute":"attr_name"...} | {..."attribute":"attr_name"...} | * +---------------------------------|---------------------------------+ * From attribute code "attr_code", will be generated such SQL: * `condition_serialized` LIKE '%"attribute":"attr_name"%' * OR `actions_serialized` LIKE '%"attribute":"attr_name"%' */ $field = $this->_getMappedField('conditions_serialized'); $cCond = $this->_getConditionSql($field, ['like' => $match]); $field = $this->_getMappedField('actions_serialized'); $aCond = $this->_getConditionSql($field, ['like' => $match]); $this->getSelect()->where( sprintf('(%s OR %s)', $cCond, $aCond), null, Select::TYPE_CONDITION ); return $this; } /** * Excludes price rules with generated specific coupon codes from collection * * @return $this */ public function addAllowedSalesRulesFilter() { $this->addFieldToFilter('main_table.use_auto_generation', ['neq' => 1]); return $this; } /** * Limit rules collection by specific customer group * * @param int $customerGroupId * @return $this * @since 100.1.0 */ public function addCustomerGroupFilter($customerGroupId) { $entityInfo = $this->_getAssociatedEntityInfo('customer_group'); if (!$this->getFlag('is_customer_group_joined')) { $this->setFlag('is_customer_group_joined', true); $this->getSelect()->join( ['customer_group' => $this->getTable($entityInfo['associations_table'])], $this->getConnection() ->quoteInto('customer_group.' . $entityInfo['entity_id_field'] . ' = ?', $customerGroupId) . ' AND main_table.' . $entityInfo['rule_id_field'] . ' = customer_group.' . $entityInfo['rule_id_field'], [] ); } return $this; } /** * Getter for _associatedEntitiesMap property * * @return array * @deprecated 100.1.0 */ private function getAssociatedEntitiesMap() { if (!$this->_associatedEntitiesMap) { $this->_associatedEntitiesMap = \Magento\Framework\App\ObjectManager::getInstance() ->get(\Magento\SalesRule\Model\ResourceModel\Rule\AssociatedEntityMap::class) ->getData(); } return $this->_associatedEntitiesMap; } /** * Getter for dateApplier property * * @return DateApplier * @deprecated 100.1.0 */ private function getDateApplier() { if (null === $this->dateApplier) { $this->dateApplier = \Magento\Framework\App\ObjectManager::getInstance() ->get(\Magento\SalesRule\Model\ResourceModel\Rule\DateApplier::class); } return $this->dateApplier; } }