123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Rule\Model\Condition\Sql;
- use Magento\Framework\App\ObjectManager;
- use Magento\Framework\DB\Select;
- use Magento\Framework\Exception\NoSuchEntityException;
- use Magento\Rule\Model\Condition\AbstractCondition;
- use Magento\Rule\Model\Condition\Combine;
- use Magento\Eav\Api\AttributeRepositoryInterface;
- use Magento\Catalog\Model\Product;
- use Magento\Eav\Model\Entity\Collection\AbstractCollection;
- /**
- * Class SQL Builder
- */
- class Builder
- {
- /**
- * @var \Magento\Framework\DB\Adapter\AdapterInterface
- */
- protected $_connection;
- /**
- * @var array
- */
- protected $_conditionOperatorMap = [
- '==' => ':field = ?',
- '!=' => ':field <> ?',
- '>=' => ':field >= ?',
- '>=' => ':field >= ?',
- '>' => ':field > ?',
- '>' => ':field > ?',
- '<=' => ':field <= ?',
- '<=' => ':field <= ?',
- '<' => ':field < ?',
- '<' => ':field < ?',
- '{}' => ':field IN (?)',
- '!{}' => ':field NOT IN (?)',
- '()' => ':field IN (?)',
- '!()' => ':field NOT IN (?)',
- ];
- /**
- * @var array
- */
- private $stringConditionOperatorMap = [
- '{}' => ':field LIKE ?',
- '!{}' => ':field NOT LIKE ?',
- ];
- /**
- * @var \Magento\Rule\Model\Condition\Sql\ExpressionFactory
- */
- protected $_expressionFactory;
- /**
- * @var AttributeRepositoryInterface
- */
- private $attributeRepository;
- /**
- * @param ExpressionFactory $expressionFactory
- * @param AttributeRepositoryInterface|null $attributeRepository
- */
- public function __construct(
- ExpressionFactory $expressionFactory,
- AttributeRepositoryInterface $attributeRepository = null
- ) {
- $this->_expressionFactory = $expressionFactory;
- $this->attributeRepository = $attributeRepository ?:
- ObjectManager::getInstance()->get(AttributeRepositoryInterface::class);
- }
- /**
- * Get tables to join for given conditions combination
- *
- * @param Combine $combine
- * @return array
- */
- protected function _getCombineTablesToJoin(Combine $combine)
- {
- $tables = $this->_getChildCombineTablesToJoin($combine);
- return $tables;
- }
- /**
- * Get child for given conditions combination
- *
- * @param Combine $combine
- * @param array $tables
- * @return array
- */
- protected function _getChildCombineTablesToJoin(Combine $combine, $tables = [])
- {
- foreach ($combine->getConditions() as $condition) {
- if ($condition->getConditions()) {
- $tables = $this->_getChildCombineTablesToJoin($condition);
- } else {
- /** @var $condition AbstractCondition */
- foreach ($condition->getTablesToJoin() as $alias => $table) {
- if (!isset($tables[$alias])) {
- $tables[$alias] = $table;
- }
- }
- }
- }
- return $tables;
- }
- /**
- * Join tables from conditions combination to collection
- *
- * @param AbstractCollection $collection
- * @param Combine $combine
- * @return $this
- */
- protected function _joinTablesToCollection(
- AbstractCollection $collection,
- Combine $combine
- ): Builder {
- foreach ($this->_getCombineTablesToJoin($combine) as $alias => $joinTable) {
- /** @var $condition AbstractCondition */
- $collection->getSelect()->joinLeft(
- [$alias => $collection->getResource()->getTable($joinTable['name'])],
- $joinTable['condition'],
- isset($joinTable['columns']) ? $joinTable['columns'] : '*'
- );
- }
- return $this;
- }
- /**
- * Returns sql expression based on rule condition.
- *
- * @param AbstractCondition $condition
- * @param string $value
- * @param bool $isDefaultStoreUsed no longer used because caused an issue about not existing table alias
- * @return string
- * @throws \Magento\Framework\Exception\LocalizedException
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
- */
- protected function _getMappedSqlCondition(
- AbstractCondition $condition,
- string $value = '',
- bool $isDefaultStoreUsed = true
- ): string {
- $argument = $condition->getMappedSqlField();
- // If rule hasn't valid argument - create negative expression to prevent incorrect rule behavior.
- if (empty($argument)) {
- return $this->_expressionFactory->create(['expression' => '1 = -1']);
- }
- $conditionOperator = $condition->getOperatorForValidate();
- if (!isset($this->_conditionOperatorMap[$conditionOperator])) {
- throw new \Magento\Framework\Exception\LocalizedException(__('Unknown condition operator'));
- }
- $defaultValue = 0;
- //operator 'contains {}' is mapped to 'IN()' query that cannot work with substrings
- // adding mapping to 'LIKE %%'
- if ($condition->getInputType() === 'string'
- && in_array($conditionOperator, array_keys($this->stringConditionOperatorMap), true)
- ) {
- $sql = str_replace(
- ':field',
- $this->_connection->getIfNullSql($this->_connection->quoteIdentifier($argument), $defaultValue),
- $this->stringConditionOperatorMap[$conditionOperator]
- );
- $bindValue = $condition->getBindArgumentValue();
- $expression = $value . $this->_connection->quoteInto($sql, "%$bindValue%");
- } else {
- $sql = str_replace(
- ':field',
- $this->_connection->getIfNullSql($this->_connection->quoteIdentifier($argument), $defaultValue),
- $this->_conditionOperatorMap[$conditionOperator]
- );
- $bindValue = $condition->getBindArgumentValue();
- $expression = $value . $this->_connection->quoteInto($sql, $bindValue);
- }
- // values for multiselect attributes can be saved in comma-separated format
- // below is a solution for matching such conditions with selected values
- if (is_array($bindValue) && \in_array($conditionOperator, ['()', '{}'], true)) {
- foreach ($bindValue as $item) {
- $expression .= $this->_connection->quoteInto(
- " OR (FIND_IN_SET (?, {$this->_connection->quoteIdentifier($argument)}) > 0)",
- $item
- );
- }
- }
- return $this->_expressionFactory->create(
- ['expression' => $expression]
- );
- }
- /**
- * Get mapped sql combination.
- *
- * @param Combine $combine
- * @param string $value
- * @param bool $isDefaultStoreUsed
- * @return string
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @throws \Magento\Framework\Exception\LocalizedException
- */
- protected function _getMappedSqlCombination(
- Combine $combine,
- string $value = '',
- bool $isDefaultStoreUsed = true
- ): string {
- $out = (!empty($value) ? $value : '');
- $value = ($combine->getValue() ? '' : ' NOT ');
- $getAggregator = $combine->getAggregator();
- $conditions = $combine->getConditions();
- foreach ($conditions as $key => $condition) {
- /** @var $condition AbstractCondition|Combine */
- $con = ($getAggregator == 'any' ? Select::SQL_OR : Select::SQL_AND);
- $con = (isset($conditions[$key+1]) ? $con : '');
- if ($condition instanceof Combine) {
- $out .= $this->_getMappedSqlCombination($condition, $value, $isDefaultStoreUsed);
- } else {
- $out .= $this->_getMappedSqlCondition($condition, $value, $isDefaultStoreUsed);
- }
- $out .= $out ? (' ' . $con) : '';
- }
- return $this->_expressionFactory->create(['expression' => $out]);
- }
- /**
- * Attach conditions filter to collection
- *
- * @param AbstractCollection $collection
- * @param Combine $combine
- * @return void
- */
- public function attachConditionToCollection(
- AbstractCollection $collection,
- Combine $combine
- ): void {
- $this->_connection = $collection->getResource()->getConnection();
- $this->_joinTablesToCollection($collection, $combine);
- $whereExpression = (string)$this->_getMappedSqlCombination($combine);
- if (!empty($whereExpression)) {
- if (!empty($combine->getConditions())) {
- $conditions = '';
- $attributeField = '';
- foreach ($combine->getConditions() as $condition) {
- if ($condition->getData('attribute') === \Magento\Catalog\Api\Data\ProductInterface::SKU) {
- $conditions = $condition->getData('value');
- $attributeField = $condition->getMappedSqlField();
- }
- }
- $collection->getSelect()->where($whereExpression);
- if (!empty($conditions) && !empty($attributeField)) {
- $conditions = explode(',', $conditions);
- foreach ($conditions as &$condition) {
- $condition = "'" . trim($condition) . "'";
- }
- $conditions = implode(', ', $conditions);
- $collection->getSelect()->order("FIELD($attributeField, $conditions)");
- }
- } else {
- // Select ::where method adds braces even on empty expression
- $collection->getSelect()->where($whereExpression);
- }
- }
- }
- }
|