diff --git a/vendor/magento/module-sales-rule/Model/Converter/ToDataModel.php b/vendor/magento/module-sales-rule/Model/Converter/ToDataModel.php
index 2db45d4d904cb..55c28d0442490 100644
--- a/vendor/magento/module-sales-rule/Model/Converter/ToDataModel.php
+++ b/vendor/magento/module-sales-rule/Model/Converter/ToDataModel.php
@@ -98,6 +98,8 @@ public function toDataModel(Rule $ruleModel)
}
/**
+ * Convert conditions from array to condition data model
+ *
* @param RuleDataModel $dataModel
* @param Rule $ruleModel
* @return $this
@@ -116,6 +118,8 @@ protected function mapConditions(RuleDataModel $dataModel, Rule $ruleModel)
}
/**
+ * Convert action conditions from array to condition data model
+ *
* @param RuleDataModel $dataModel
* @param Rule $ruleModel
* @return $this
@@ -134,6 +138,8 @@ protected function mapActionConditions(RuleDataModel $dataModel, Rule $ruleModel
}
/**
+ * Convert store labels from associative array to array of objects with store_id and store_label fields
+ *
* @param RuleDataModel $dataModel
* @return $this
*/
@@ -154,6 +160,8 @@ protected function mapStoreLabels(RuleDataModel $dataModel)
}
/**
+ * Convert coupon type ID to its string representation in data model
+ *
* @param RuleDataModel $dataModel
* @return $this
*/
@@ -194,6 +202,8 @@ private function convertExtensionAttributesToObject(array $data)
}
/**
+ * Convert rule model fields to data model fields
+ *
* @param RuleDataModel $dataModel
* @param Rule $ruleModel
* @return $this
@@ -225,6 +235,11 @@ public function arrayToConditionDataModel(array $input)
case 'attribute':
$conditionDataModel->setAttributeName($value);
break;
+ case 'attribute_scope':
+ $extensions = $conditionDataModel->getExtensionAttributes();
+ $extensions->setAttributeScope($value);
+ $conditionDataModel->setExtensionAttributes($extensions);
+ break;
case 'operator':
$conditionDataModel->setOperator($value);
break;
@@ -235,10 +250,7 @@ public function arrayToConditionDataModel(array $input)
$conditionDataModel->setAggregatorType($value);
break;
case 'conditions':
- $conditions = [];
- foreach ($value as $condition) {
- $conditions[] = $this->arrayToConditionDataModel($condition);
- }
+ $conditions = array_values(array_map($this->arrayToConditionDataModel(...), $value));
$conditionDataModel->setConditions($conditions);
break;
default:
diff --git a/vendor/magento/module-sales-rule/Model/Converter/ToModel.php b/vendor/magento/module-sales-rule/Model/Converter/ToModel.php
index 958d76b131682..85642c983ad20 100644
--- a/vendor/magento/module-sales-rule/Model/Converter/ToModel.php
+++ b/vendor/magento/module-sales-rule/Model/Converter/ToModel.php
@@ -5,9 +5,12 @@
*/
namespace Magento\SalesRule\Model\Converter;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Exception\InputException;
use Magento\SalesRule\Api\Data\RuleInterface;
use Magento\SalesRule\Model\Data\Condition;
use Magento\SalesRule\Model\Data\Rule as RuleDataModel;
+use Magento\SalesRule\Model\Data\Validator;
use Magento\SalesRule\Model\Rule;
class ToModel
@@ -24,16 +27,24 @@ class ToModel
*/
protected $dataObjectProcessor;
+ /**
+ * @var Validator
+ */
+ private Validator $validator;
+
/**
* @param \Magento\SalesRule\Model\RuleFactory $ruleFactory
* @param \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor
+ * @param Validator|null $validator
*/
public function __construct(
\Magento\SalesRule\Model\RuleFactory $ruleFactory,
- \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor
+ \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor,
+ ?Validator $validator = null
) {
$this->ruleFactory = $ruleFactory;
$this->dataObjectProcessor = $dataObjectProcessor;
+ $this->validator = $validator ?? ObjectManager::getInstance()->get(Validator::class);
}
/**
@@ -150,6 +161,9 @@ public function dataModelToArray(Condition $condition, $key = 'conditions')
$output['value'] = $condition->getValue();
$output['attribute'] = $condition->getAttributeName();
$output['operator'] = $condition->getOperator();
+ if ($condition->getExtensionAttributes()?->getAttributeScope()) {
+ $output['attribute_scope'] = $condition->getExtensionAttributes()->getAttributeScope();
+ }
if ($condition->getAggregatorType()) {
$output['aggregator'] = $condition->getAggregatorType();
@@ -170,7 +184,7 @@ public function dataModelToArray(Condition $condition, $key = 'conditions')
* @param RuleDataModel $dataModel
* @return $this|Rule
* @throws \Magento\Framework\Exception\NoSuchEntityException
- * @throws \Magento\Framework\Exception\InputException
+ * @throws InputException
*/
public function toModel(RuleDataModel $dataModel)
{
@@ -204,20 +218,22 @@ public function toModel(RuleDataModel $dataModel)
$mergedData = array_merge($modelData, $data);
$validateResult = $ruleModel->validateData(new \Magento\Framework\DataObject($mergedData));
- if ($validateResult !== true) {
- $text = '';
- /** @var \Magento\Framework\Phrase $errorMessage */
- foreach ($validateResult as $errorMessage) {
- $text .= $errorMessage->getText();
- $text .= '; ';
- }
- throw new \Magento\Framework\Exception\InputException(new \Magento\Framework\Phrase($text));
- }
+ $validationErrors = is_array($validateResult) ? $validateResult : [];
$ruleModel->setData($mergedData);
$this->mapFields($ruleModel, $dataModel);
+ if (!$this->validator->isValid($dataModel)) {
+ $validationErrors = array_merge($validationErrors, $this->validator->getMessages());
+ }
+
+ if ($validationErrors) {
+ $exception = new InputException();
+ array_walk($validationErrors, $exception->addError(...));
+ throw $exception;
+ }
+
return $ruleModel;
}
diff --git a/vendor/magento/module-sales-rule/Model/Data/Condition/Validator.php b/vendor/magento/module-sales-rule/Model/Data/Condition/Validator.php
new file mode 100644
index 0000000000000..176fae8c705c9
--- /dev/null
+++ b/vendor/magento/module-sales-rule/Model/Data/Condition/Validator.php
@@ -0,0 +1,57 @@
+_clearMessages();
+ if (!$value instanceof Rule) {
+ throw new InvalidArgumentException('Expected instance of ' . Rule::class);
+ }
+ if ($value->getCondition()) {
+ $this->validate($value->getCondition());
+ }
+ if ($value->getActionCondition()) {
+ $this->validate($value->getActionCondition());
+ }
+ return empty($this->getMessages());
+ }
+
+ /**
+ * Validate condition attributes
+ *
+ * @param Condition $condition
+ * @return void
+ */
+ private function validate(Condition $condition): void
+ {
+ $scope = $condition->getExtensionAttributes()?->getAttributeScope();
+ if ($scope && !in_array($scope, ['parent', 'children'], true)) {
+ $this->_addMessages([__(
+ 'Invalid value of "%value" provided for the %fieldName field.',
+ ['fieldName' => 'attribute_scope', 'value' => $scope]
+ )]);
+ }
+ if ($condition->getConditions()) {
+ foreach ($condition->getConditions() as $condition) {
+ $this->validate($condition);
+ }
+ }
+ }
+}
diff --git a/vendor/magento/module-sales-rule/Model/Data/Validator.php b/vendor/magento/module-sales-rule/Model/Data/Validator.php
new file mode 100644
index 0000000000000..91c6c8dac7b83
--- /dev/null
+++ b/vendor/magento/module-sales-rule/Model/Data/Validator.php
@@ -0,0 +1,38 @@
+ $validator, $validators);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isValid($value)
+ {
+ $this->_clearMessages();
+ foreach ($this->validators as $validator) {
+ if (!$validator->isValid($value)) {
+ $this->_addMessages($validator->getMessages());
+ }
+ }
+ return empty($this->getMessages());
+ }
+}
diff --git a/vendor/magento/module-sales-rule/etc/di.xml b/vendor/magento/module-sales-rule/etc/di.xml
index cd841b36338e4..8500e6d358828 100644
--- a/vendor/magento/module-sales-rule/etc/di.xml
+++ b/vendor/magento/module-sales-rule/etc/di.xml
@@ -212,4 +212,11 @@
+
+
+
+ - Magento\SalesRule\Model\Data\Condition\Validator
+
+
+
diff --git a/vendor/magento/module-sales-rule/etc/extension_attributes.xml b/vendor/magento/module-sales-rule/etc/extension_attributes.xml
index 8cdf0fd7cd39c..d90c0d6d1ec42 100644
--- a/vendor/magento/module-sales-rule/etc/extension_attributes.xml
+++ b/vendor/magento/module-sales-rule/etc/extension_attributes.xml
@@ -15,4 +15,7 @@
+
+
+