-
+
getFileThumbUrl($file)):?>
diff --git a/app/code/Magento/Config/App/Config/Type/System.php b/app/code/Magento/Config/App/Config/Type/System.php
index 83c61f90f789a..c07872a630830 100644
--- a/app/code/Magento/Config/App/Config/Type/System.php
+++ b/app/code/Magento/Config/App/Config/Type/System.php
@@ -11,6 +11,7 @@
use Magento\Framework\App\Config\Spi\PreProcessorInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Config\App\Config\Type\System\Reader;
+use Magento\Framework\Lock\LockManagerInterface;
use Magento\Framework\Serialize\Serializer\Sensitive as SensitiveSerializer;
use Magento\Framework\Serialize\Serializer\SensitiveFactory as SensitiveSerializerFactory;
use Magento\Framework\App\ScopeInterface;
@@ -24,12 +25,43 @@
*
* @api
* @since 100.1.2
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class System implements ConfigTypeInterface
{
+ /**
+ * Config cache tag.
+ */
const CACHE_TAG = 'config_scopes';
+
+ /**
+ * System config type.
+ */
const CONFIG_TYPE = 'system';
+ /**
+ * @var string
+ */
+ private static $lockName = 'SYSTEM_CONFIG';
+
+ /**
+ * Timeout between retrieves to load the configuration from the cache.
+ *
+ * Value of the variable in microseconds.
+ *
+ * @var int
+ */
+ private static $delayTimeout = 50000;
+
+ /**
+ * Lifetime of the lock for write in cache.
+ *
+ * Value of the variable in seconds.
+ *
+ * @var int
+ */
+ private static $lockTimeout = 8;
+
/**
* @var array
*/
@@ -71,6 +103,11 @@ class System implements ConfigTypeInterface
*/
private $availableDataScopes;
+ /**
+ * @var LockManagerInterface
+ */
+ private $locker;
+
/**
* @param ConfigSourceInterface $source
* @param PostProcessorInterface $postProcessor
@@ -82,7 +119,7 @@ class System implements ConfigTypeInterface
* @param string $configType
* @param Reader $reader
* @param SensitiveSerializerFactory|null $sensitiveFactory
- *
+ * @param LockManagerInterface|null $locker
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
@@ -96,7 +133,8 @@ public function __construct(
$cachingNestedLevel = 1,
$configType = self::CONFIG_TYPE,
Reader $reader = null,
- SensitiveSerializerFactory $sensitiveFactory = null
+ SensitiveSerializerFactory $sensitiveFactory = null,
+ LockManagerInterface $locker = null
) {
$this->postProcessor = $postProcessor;
$this->cache = $cache;
@@ -110,6 +148,7 @@ public function __construct(
$this->serializer = $sensitiveFactory->create(
['serializer' => $serializer]
);
+ $this->locker = $locker ?: ObjectManager::getInstance()->get(LockManagerInterface::class);
}
/**
@@ -153,7 +192,7 @@ private function getWithParts($path)
if (count($pathParts) === 1 && $pathParts[0] !== ScopeInterface::SCOPE_DEFAULT) {
if (!isset($this->data[$pathParts[0]])) {
- $data = $this->readData();
+ $data = $this->loadAllData();
$this->data = array_replace_recursive($data, $this->data);
}
@@ -186,21 +225,60 @@ private function getWithParts($path)
}
/**
- * Load configuration data for all scopes
+ * Make lock on data load.
*
+ * @param callable $dataLoader
+ * @param bool $flush
* @return array
*/
- private function loadAllData()
+ private function lockedLoadData(callable $dataLoader, bool $flush = false): array
{
- $cachedData = $this->cache->load($this->configType);
+ $cachedData = $dataLoader(); //optimistic read
- if ($cachedData === false) {
- $data = $this->readData();
- } else {
- $data = $this->serializer->unserialize($cachedData);
+ while ($cachedData === false && $this->locker->isLocked(self::$lockName)) {
+ usleep(self::$delayTimeout);
+ $cachedData = $dataLoader();
}
- return $data;
+ while ($cachedData === false) {
+ try {
+ if ($this->locker->lock(self::$lockName, self::$lockTimeout)) {
+ if (!$flush) {
+ $data = $this->readData();
+ $this->cacheData($data);
+ $cachedData = $data;
+ } else {
+ $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]);
+ $cachedData = [];
+ }
+ }
+ } finally {
+ $this->locker->unlock(self::$lockName);
+ }
+
+ if ($cachedData === false) {
+ usleep(self::$delayTimeout);
+ $cachedData = $dataLoader();
+ }
+ }
+
+ return $cachedData;
+ }
+
+ /**
+ * Load configuration data for all scopes
+ *
+ * @return array
+ */
+ private function loadAllData()
+ {
+ return $this->lockedLoadData(function () {
+ $cachedData = $this->cache->load($this->configType);
+ if ($cachedData === false) {
+ return $cachedData;
+ }
+ return $this->serializer->unserialize($cachedData);
+ });
}
/**
@@ -211,16 +289,13 @@ private function loadAllData()
*/
private function loadDefaultScopeData($scopeType)
{
- $cachedData = $this->cache->load($this->configType . '_' . $scopeType);
-
- if ($cachedData === false) {
- $data = $this->readData();
- $this->cacheData($data);
- } else {
- $data = [$scopeType => $this->serializer->unserialize($cachedData)];
- }
-
- return $data;
+ return $this->lockedLoadData(function () use ($scopeType) {
+ $cachedData = $this->cache->load($this->configType . '_' . $scopeType);
+ if ($cachedData === false) {
+ return $cachedData;
+ }
+ return [$scopeType => $this->serializer->unserialize($cachedData)];
+ });
}
/**
@@ -232,25 +307,22 @@ private function loadDefaultScopeData($scopeType)
*/
private function loadScopeData($scopeType, $scopeId)
{
- $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId);
-
- if ($cachedData === false) {
- if ($this->availableDataScopes === null) {
- $cachedScopeData = $this->cache->load($this->configType . '_scopes');
- if ($cachedScopeData !== false) {
- $this->availableDataScopes = $this->serializer->unserialize($cachedScopeData);
+ return $this->lockedLoadData(function () use ($scopeType, $scopeId) {
+ $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId);
+ if ($cachedData === false) {
+ if ($this->availableDataScopes === null) {
+ $cachedScopeData = $this->cache->load($this->configType . '_scopes');
+ if ($cachedScopeData !== false) {
+ $this->availableDataScopes = $this->serializer->unserialize($cachedScopeData);
+ }
}
+ if (is_array($this->availableDataScopes) && !isset($this->availableDataScopes[$scopeType][$scopeId])) {
+ return [$scopeType => [$scopeId => []]];
+ }
+ return false;
}
- if (is_array($this->availableDataScopes) && !isset($this->availableDataScopes[$scopeType][$scopeId])) {
- return [$scopeType => [$scopeId => []]];
- }
- $data = $this->readData();
- $this->cacheData($data);
- } else {
- $data = [$scopeType => [$scopeId => $this->serializer->unserialize($cachedData)]];
- }
-
- return $data;
+ return [$scopeType => [$scopeId => $this->serializer->unserialize($cachedData)]];
+ });
}
/**
@@ -340,6 +412,11 @@ private function readData(): array
public function clean()
{
$this->data = [];
- $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]);
+ $this->lockedLoadData(
+ function () {
+ return false;
+ },
+ true
+ );
}
}
diff --git a/app/code/Magento/Config/Block/System/Config/Form.php b/app/code/Magento/Config/Block/System/Config/Form.php
index 81e39a83296d7..a05151153daa2 100644
--- a/app/code/Magento/Config/Block/System/Config/Form.php
+++ b/app/code/Magento/Config/Block/System/Config/Form.php
@@ -143,13 +143,15 @@ public function __construct(
\Magento\Config\Model\Config\Structure $configStructure,
\Magento\Config\Block\System\Config\Form\Fieldset\Factory $fieldsetFactory,
\Magento\Config\Block\System\Config\Form\Field\Factory $fieldFactory,
- array $data = []
+ array $data = [],
+ SettingChecker $settingChecker = null
) {
parent::__construct($context, $registry, $formFactory, $data);
$this->_configFactory = $configFactory;
$this->_configStructure = $configStructure;
$this->_fieldsetFactory = $fieldsetFactory;
$this->_fieldFactory = $fieldFactory;
+ $this->settingChecker = $settingChecker ?: ObjectManager::getInstance()->get(SettingChecker::class);
$this->_scopeLabels = [
self::SCOPE_DEFAULT => __('[GLOBAL]'),
@@ -158,18 +160,6 @@ public function __construct(
];
}
- /**
- * @deprecated 100.1.2
- * @return SettingChecker
- */
- private function getSettingChecker()
- {
- if ($this->settingChecker === null) {
- $this->settingChecker = ObjectManager::getInstance()->get(SettingChecker::class);
- }
- return $this->settingChecker;
- }
-
/**
* Initialize objects required to render config form
*
@@ -366,9 +356,8 @@ protected function _initElement(
$sharedClass = $this->_getSharedCssClass($field);
$requiresClass = $this->_getRequiresCssClass($field, $fieldPrefix);
+ $isReadOnly = $this->isReadOnly($field, $path);
- $isReadOnly = $this->getElementVisibility()->isDisabled($field->getPath())
- ?: $this->getSettingChecker()->isReadOnly($path, $this->getScope(), $this->getStringScopeCode());
$formField = $fieldset->addField(
$elementId,
$field->getType(),
@@ -417,7 +406,7 @@ private function getFieldData(\Magento\Config\Model\Config\Structure\Element\Fie
{
$data = $this->getAppConfigDataValue($path);
- $placeholderValue = $this->getSettingChecker()->getPlaceholderValue(
+ $placeholderValue = $this->settingChecker->getPlaceholderValue(
$path,
$this->getScope(),
$this->getStringScopeCode()
@@ -718,6 +707,7 @@ protected function _getAdditionalElementTypes()
*
* @TODO delete this methods when {^see above^} is done
* @return string
+ * @SuppressWarnings(PHPMD.RequestAwareBlockMethod)
*/
public function getSectionCode()
{
@@ -729,6 +719,7 @@ public function getSectionCode()
*
* @TODO delete this methods when {^see above^} is done
* @return string
+ * @SuppressWarnings(PHPMD.RequestAwareBlockMethod)
*/
public function getWebsiteCode()
{
@@ -740,6 +731,7 @@ public function getWebsiteCode()
*
* @TODO delete this methods when {^see above^} is done
* @return string
+ * @SuppressWarnings(PHPMD.RequestAwareBlockMethod)
*/
public function getStoreCode()
{
@@ -797,6 +789,26 @@ private function getAppConfig()
return $this->appConfig;
}
+ /**
+ * Check Path is Readonly
+ *
+ * @param \Magento\Config\Model\Config\Structure\Element\Field $field
+ * @param string $path
+ * @return boolean
+ */
+ private function isReadOnly(\Magento\Config\Model\Config\Structure\Element\Field $field, $path)
+ {
+ $isReadOnly = $this->settingChecker->isReadOnly(
+ $path,
+ ScopeConfigInterface::SCOPE_TYPE_DEFAULT
+ );
+ if (!$isReadOnly) {
+ $isReadOnly = $this->getElementVisibility()->isDisabled($field->getPath())
+ ?: $this->settingChecker->isReadOnly($path, $this->getScope(), $this->getStringScopeCode());
+ }
+ return $isReadOnly;
+ }
+
/**
* Retrieve deployment config data value by path
*
diff --git a/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php b/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php
index d7d513bfad423..86ae1f96749df 100644
--- a/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php
+++ b/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php
@@ -7,17 +7,19 @@
use Magento\Config\App\Config\Type\System;
use Magento\Config\Console\Command\ConfigSetCommand;
+use Magento\Config\Model\Config\Factory as ConfigFactory;
use Magento\Framework\App\Config\ConfigPathResolver;
use Magento\Framework\App\DeploymentConfig;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Config\Model\PreparedValueFactory;
-use Magento\Framework\App\Config\Value;
/**
* Processes default flow of config:set command.
+ *
* This processor saves the value of configuration into database.
*
- * {@inheritdoc}
+ * @inheritdoc
* @api
* @since 100.2.0
*/
@@ -44,26 +46,36 @@ class DefaultProcessor implements ConfigSetProcessorInterface
*/
private $preparedValueFactory;
+ /**
+ * @var ConfigFactory
+ */
+ private $configFactory;
+
/**
* @param PreparedValueFactory $preparedValueFactory The factory for prepared value
* @param DeploymentConfig $deploymentConfig The deployment configuration reader
* @param ConfigPathResolver $configPathResolver The resolver for configuration paths according to source type
+ * @param ConfigFactory|null $configFactory
*/
public function __construct(
PreparedValueFactory $preparedValueFactory,
DeploymentConfig $deploymentConfig,
- ConfigPathResolver $configPathResolver
+ ConfigPathResolver $configPathResolver,
+ ConfigFactory $configFactory = null
) {
$this->preparedValueFactory = $preparedValueFactory;
$this->deploymentConfig = $deploymentConfig;
$this->configPathResolver = $configPathResolver;
+
+ $this->configFactory = $configFactory ?? ObjectManager::getInstance()->get(ConfigFactory::class);
}
/**
* Processes database flow of config:set command.
+ *
* Requires installed application.
*
- * {@inheritdoc}
+ * @inheritdoc
* @since 100.2.0
*/
public function process($path, $value, $scope, $scopeCode)
@@ -78,12 +90,12 @@ public function process($path, $value, $scope, $scopeCode)
}
try {
- /** @var Value $backendModel */
- $backendModel = $this->preparedValueFactory->create($path, $value, $scope, $scopeCode);
- if ($backendModel instanceof Value) {
- $resourceModel = $backendModel->getResource();
- $resourceModel->save($backendModel);
- }
+ $config = $this->configFactory->create([
+ 'scope' => $scope,
+ 'scope_code' => $scopeCode,
+ ]);
+ $config->setDataByPath($path, $value);
+ $config->save();
} catch (\Exception $exception) {
throw new CouldNotSaveException(__('%1', $exception->getMessage()), $exception);
}
diff --git a/app/code/Magento/Config/Model/Config.php b/app/code/Magento/Config/Model/Config.php
index 0472c5daa276f..b1074e92cc949 100644
--- a/app/code/Magento/Config/Model/Config.php
+++ b/app/code/Magento/Config/Model/Config.php
@@ -9,15 +9,32 @@
use Magento\Config\Model\Config\Structure\Element\Group;
use Magento\Config\Model\Config\Structure\Element\Field;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\App\ScopeInterface;
+use Magento\Framework\App\ScopeResolverPool;
+use Magento\Store\Model\ScopeInterface as StoreScopeInterface;
+use Magento\Store\Model\ScopeTypeNormalizer;
/**
* Backend config model
+ *
* Used to save configuration
*
* @author Magento Core Team
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @api
* @since 100.0.2
+ * @method string getSection()
+ * @method void setSection(string $section)
+ * @method string getWebsite()
+ * @method void setWebsite(string $website)
+ * @method string getStore()
+ * @method void setStore(string $store)
+ * @method string getScope()
+ * @method void setScope(string $scope)
+ * @method int getScopeId()
+ * @method void setScopeId(int $scopeId)
+ * @method string getScopeCode()
+ * @method void setScopeCode(string $scopeCode)
*/
class Config extends \Magento\Framework\DataObject
{
@@ -87,6 +104,16 @@ class Config extends \Magento\Framework\DataObject
*/
private $settingChecker;
+ /**
+ * @var ScopeResolverPool
+ */
+ private $scopeResolverPool;
+
+ /**
+ * @var ScopeTypeNormalizer
+ */
+ private $scopeTypeNormalizer;
+
/**
* @param \Magento\Framework\App\Config\ReinitableConfigInterface $config
* @param \Magento\Framework\Event\ManagerInterface $eventManager
@@ -97,6 +124,9 @@ class Config extends \Magento\Framework\DataObject
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param Config\Reader\Source\Deployed\SettingChecker|null $settingChecker
* @param array $data
+ * @param ScopeResolverPool|null $scopeResolverPool
+ * @param ScopeTypeNormalizer|null $scopeTypeNormalizer
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Framework\App\Config\ReinitableConfigInterface $config,
@@ -107,7 +137,9 @@ public function __construct(
\Magento\Framework\App\Config\ValueFactory $configValueFactory,
\Magento\Store\Model\StoreManagerInterface $storeManager,
SettingChecker $settingChecker = null,
- array $data = []
+ array $data = [],
+ ScopeResolverPool $scopeResolverPool = null,
+ ScopeTypeNormalizer $scopeTypeNormalizer = null
) {
parent::__construct($data);
$this->_eventManager = $eventManager;
@@ -117,11 +149,17 @@ public function __construct(
$this->_configLoader = $configLoader;
$this->_configValueFactory = $configValueFactory;
$this->_storeManager = $storeManager;
- $this->settingChecker = $settingChecker ?: ObjectManager::getInstance()->get(SettingChecker::class);
+ $this->settingChecker = $settingChecker
+ ?? ObjectManager::getInstance()->get(SettingChecker::class);
+ $this->scopeResolverPool = $scopeResolverPool
+ ?? ObjectManager::getInstance()->get(ScopeResolverPool::class);
+ $this->scopeTypeNormalizer = $scopeTypeNormalizer
+ ?? ObjectManager::getInstance()->get(ScopeTypeNormalizer::class);
}
/**
* Save config section
+ *
* Require set: section, website, store and groups
*
* @throws \Exception
@@ -504,8 +542,8 @@ public function setDataByPath($path, $value)
}
/**
- * Get scope name and scopeId
- * @todo refactor to scope resolver
+ * Set scope data
+ *
* @return void
*/
private function initScope()
@@ -513,31 +551,66 @@ private function initScope()
if ($this->getSection() === null) {
$this->setSection('');
}
+
+ $scope = $this->retrieveScope();
+ $this->setScope($this->scopeTypeNormalizer->normalize($scope->getScopeType()));
+ $this->setScopeCode($scope->getCode());
+ $this->setScopeId($scope->getId());
+
if ($this->getWebsite() === null) {
- $this->setWebsite('');
+ $this->setWebsite(StoreScopeInterface::SCOPE_WEBSITES === $this->getScope() ? $scope->getId() : '');
}
if ($this->getStore() === null) {
- $this->setStore('');
+ $this->setStore(StoreScopeInterface::SCOPE_STORES === $this->getScope() ? $scope->getId() : '');
}
+ }
- if ($this->getStore()) {
- $scope = 'stores';
- $store = $this->_storeManager->getStore($this->getStore());
- $scopeId = (int)$store->getId();
- $scopeCode = $store->getCode();
- } elseif ($this->getWebsite()) {
- $scope = 'websites';
- $website = $this->_storeManager->getWebsite($this->getWebsite());
- $scopeId = (int)$website->getId();
- $scopeCode = $website->getCode();
+ /**
+ * Retrieve scope from initial data
+ *
+ * @return ScopeInterface
+ */
+ private function retrieveScope(): ScopeInterface
+ {
+ $scopeType = $this->getScope();
+ if (!$scopeType) {
+ switch (true) {
+ case $this->getStore():
+ $scopeType = StoreScopeInterface::SCOPE_STORES;
+ $scopeIdentifier = $this->getStore();
+ break;
+ case $this->getWebsite():
+ $scopeType = StoreScopeInterface::SCOPE_WEBSITES;
+ $scopeIdentifier = $this->getWebsite();
+ break;
+ default:
+ $scopeType = ScopeInterface::SCOPE_DEFAULT;
+ $scopeIdentifier = null;
+ break;
+ }
} else {
- $scope = 'default';
- $scopeId = 0;
- $scopeCode = '';
+ switch (true) {
+ case $this->getScopeId() !== null:
+ $scopeIdentifier = $this->getScopeId();
+ break;
+ case $this->getScopeCode() !== null:
+ $scopeIdentifier = $this->getScopeCode();
+ break;
+ case $this->getStore() !== null:
+ $scopeIdentifier = $this->getStore();
+ break;
+ case $this->getWebsite() !== null:
+ $scopeIdentifier = $this->getWebsite();
+ break;
+ default:
+ $scopeIdentifier = null;
+ break;
+ }
}
- $this->setScope($scope);
- $this->setScopeId($scopeId);
- $this->setScopeCode($scopeCode);
+ $scope = $this->scopeResolverPool->get($scopeType)
+ ->getScope($scopeIdentifier);
+
+ return $scope;
}
/**
diff --git a/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php b/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php
index 4ae66bfd9692b..25303093ace5d 100644
--- a/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php
+++ b/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php
@@ -14,6 +14,8 @@
namespace Magento\Config\Model\Config\Backend\Currency;
/**
+ * Base currency class
+ *
* @api
* @since 100.0.2
*/
@@ -26,18 +28,19 @@ abstract class AbstractCurrency extends \Magento\Framework\App\Config\Value
*/
protected function _getAllowedCurrencies()
{
- if (!$this->isFormData() || $this->getData('groups/options/fields/allow/inherit')) {
- return explode(
+ $allowValue = $this->getData('groups/options/fields/allow/value');
+ $allowedCurrencies = $allowValue === null || $this->getData('groups/options/fields/allow/inherit')
+ ? explode(
',',
(string)$this->_config->getValue(
\Magento\Directory\Model\Currency::XML_PATH_CURRENCY_ALLOW,
$this->getScope(),
$this->getScopeId()
)
- );
- }
+ )
+ : (array) $allowValue;
- return (array)$this->getData('groups/options/fields/allow/value');
+ return $allowedCurrencies;
}
/**
diff --git a/app/code/Magento/Config/Setup/ConfigOptionsList.php b/app/code/Magento/Config/Setup/ConfigOptionsList.php
new file mode 100644
index 0000000000000..45e3987d282f1
--- /dev/null
+++ b/app/code/Magento/Config/Setup/ConfigOptionsList.php
@@ -0,0 +1,88 @@
+configDataFactory = $configDataFactory;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getOptions()
+ {
+ return [
+ new SelectConfigOption(
+ self::INPUT_KEY_DEBUG_LOGGING,
+ SelectConfigOption::FRONTEND_WIZARD_RADIO,
+ [true, false, 1, 0],
+ self::CONFIG_PATH_DEBUG_LOGGING,
+ 'Enable debug logging'
+ )
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function createConfig(array $options, DeploymentConfig $deploymentConfig)
+ {
+ $config = [];
+ if (isset($options[self::INPUT_KEY_DEBUG_LOGGING])) {
+ $configData = $this->configDataFactory->create(ConfigFilePool::APP_ENV);
+ if ($options[self::INPUT_KEY_DEBUG_LOGGING] === 'true'
+ || $options[self::INPUT_KEY_DEBUG_LOGGING] === '1') {
+ $value = 1;
+ } else {
+ $value = 0;
+ }
+ $configData->set(self::CONFIG_PATH_DEBUG_LOGGING, $value);
+ $config[] = $configData;
+ }
+
+ return $config;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validate(array $options, DeploymentConfig $deploymentConfig)
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminSalesConfigSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminSalesConfigSection.xml
index c58e77d200bfb..62b5cdcf9ceec 100644
--- a/app/code/Magento/Config/Test/Mftf/Section/AdminSalesConfigSection.xml
+++ b/app/code/Magento/Config/Test/Mftf/Section/AdminSalesConfigSection.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php
index 83b7bd5fda42e..528d141306cce 100644
--- a/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php
+++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php
@@ -103,6 +103,9 @@ protected function setUp()
$this->_fieldsetFactoryMock = $this->createMock(\Magento\Config\Block\System\Config\Form\Fieldset\Factory::class);
$this->_fieldFactoryMock = $this->createMock(\Magento\Config\Block\System\Config\Form\Field\Factory::class);
$this->_coreConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class);
+ $settingCheckerMock = $this->getMockBuilder(SettingChecker::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->_backendConfigMock = $this->createMock(\Magento\Config\Model\Config::class);
@@ -150,6 +153,7 @@ protected function setUp()
'fieldsetFactory' => $this->_fieldsetFactoryMock,
'fieldFactory' => $this->_fieldFactoryMock,
'context' => $context,
+ 'settingChecker' => $settingCheckerMock,
];
$objectArguments = $helper->getConstructArguments(\Magento\Config\Block\System\Config\Form::class, $data);
@@ -529,7 +533,7 @@ public function testInitFields(
$elementVisibilityMock = $this->getMockBuilder(ElementVisibilityInterface::class)
->getMockForAbstractClass();
- $elementVisibilityMock->expects($this->once())
+ $elementVisibilityMock->expects($this->any())
->method('isDisabled')
->willReturn($isDisabled);
diff --git a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php
index 984e0fe842687..3fb7d9ad21cd4 100644
--- a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php
+++ b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php
@@ -7,13 +7,14 @@
use Magento\Config\App\Config\Type\System;
use Magento\Config\Console\Command\ConfigSet\DefaultProcessor;
+use Magento\Config\Model\Config;
+use Magento\Config\Model\Config\Factory as ConfigFactory;
use Magento\Framework\App\Config\ConfigPathResolver;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\DeploymentConfig;
use Magento\Store\Model\ScopeInterface;
use Magento\Config\Model\PreparedValueFactory;
use Magento\Framework\App\Config\Value;
-use Magento\Framework\App\Config\ValueInterface;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use PHPUnit_Framework_MockObject_MockObject as Mock;
@@ -55,17 +56,18 @@ class DefaultProcessorTest extends \PHPUnit\Framework\TestCase
*/
private $resourceModelMock;
+ /**
+ * @var ConfigFactory|Mock
+ */
+ private $configFactory;
+
/**
* @inheritdoc
*/
protected function setUp()
{
- $this->deploymentConfigMock = $this->getMockBuilder(DeploymentConfig::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->configPathResolverMock = $this->getMockBuilder(ConfigPathResolver::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $this->deploymentConfigMock = $this->createMock(DeploymentConfig::class);
+ $this->configPathResolverMock = $this->createMock(ConfigPathResolver::class);
$this->resourceModelMock = $this->getMockBuilder(AbstractDb::class)
->disableOriginalConstructor()
->setMethods(['save'])
@@ -74,14 +76,14 @@ protected function setUp()
->disableOriginalConstructor()
->setMethods(['getResource'])
->getMock();
- $this->preparedValueFactoryMock = $this->getMockBuilder(PreparedValueFactory::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $this->preparedValueFactoryMock = $this->createMock(PreparedValueFactory::class);
+ $this->configFactory = $this->createMock(ConfigFactory::class);
$this->model = new DefaultProcessor(
$this->preparedValueFactoryMock,
$this->deploymentConfigMock,
- $this->configPathResolverMock
+ $this->configPathResolverMock,
+ $this->configFactory
);
}
@@ -98,15 +100,14 @@ public function testProcess($path, $value, $scope, $scopeCode)
{
$this->configMockForProcessTest($path, $scope, $scopeCode);
- $this->preparedValueFactoryMock->expects($this->once())
- ->method('create')
- ->willReturn($this->valueMock);
- $this->valueMock->expects($this->once())
- ->method('getResource')
- ->willReturn($this->resourceModelMock);
- $this->resourceModelMock->expects($this->once())
+ $config = $this->createMock(Config::class);
+ $this->configFactory->method('create')
+ ->with(['scope' => $scope, 'scope_code' => $scopeCode])
+ ->willReturn($config);
+ $config->method('setDataByPath')
+ ->with($path, $value);
+ $config->expects($this->once())
->method('save')
- ->with($this->valueMock)
->willReturnSelf();
$this->model->process($path, $value, $scope, $scopeCode);
@@ -124,28 +125,6 @@ public function processDataProvider()
];
}
- public function testProcessWithWrongValueInstance()
- {
- $path = 'test/test/test';
- $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT;
- $scopeCode = null;
- $value = 'value';
- $valueInterfaceMock = $this->getMockBuilder(ValueInterface::class)
- ->getMockForAbstractClass();
-
- $this->configMockForProcessTest($path, $scope, $scopeCode);
-
- $this->preparedValueFactoryMock->expects($this->once())
- ->method('create')
- ->willReturn($valueInterfaceMock);
- $this->valueMock->expects($this->never())
- ->method('getResource');
- $this->resourceModelMock->expects($this->never())
- ->method('save');
-
- $this->model->process($path, $value, $scope, $scopeCode);
- }
-
/**
* @param string $path
* @param string $scope
@@ -185,6 +164,9 @@ public function testProcessLockedValue()
->method('resolve')
->willReturn('system/default/test/test/test');
+ $this->configFactory->expects($this->never())
+ ->method('create');
+
$this->model->process($path, $value, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null);
}
}
diff --git a/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php b/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php
index fcc1ff8b9c70c..bb772f51c0dac 100644
--- a/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php
+++ b/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php
@@ -5,138 +5,183 @@
*/
namespace Magento\Config\Test\Unit\Model;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+use Magento\Config\Model\Config;
+use Magento\Framework\App\Config\ReinitableConfigInterface;
+use Magento\Framework\Event\ManagerInterface;
+use Magento\Config\Model\Config\Structure\Reader;
+use Magento\Framework\DB\TransactionFactory;
+use Magento\Config\Model\Config\Loader;
+use Magento\Framework\App\Config\ValueFactory;
+use Magento\Store\Model\StoreManagerInterface;
+use Magento\Config\Model\Config\Structure;
+use Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker;
+use Magento\Framework\App\ScopeResolverPool;
+use Magento\Framework\App\ScopeResolverInterface;
+use Magento\Framework\App\ScopeInterface;
+use Magento\Store\Model\ScopeTypeNormalizer;
+use Magento\Framework\DB\Transaction;
+use Magento\Framework\App\Config\Value;
+use Magento\Store\Model\Website;
+use Magento\Config\Model\Config\Structure\Element\Group;
+use Magento\Config\Model\Config\Structure\Element\Field;
+
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ConfigTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var \Magento\Config\Model\Config
+ * @var Config
+ */
+ private $model;
+
+ /**
+ * @var ManagerInterface|MockObject
+ */
+ private $eventManagerMock;
+
+ /**
+ * @var Reader|MockObject
+ */
+ private $structureReaderMock;
+
+ /**
+ * @var TransactionFactory|MockObject
*/
- protected $_model;
+ private $transFactoryMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var ReinitableConfigInterface|MockObject
*/
- protected $_eventManagerMock;
+ private $appConfigMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var Loader|MockObject
*/
- protected $_structureReaderMock;
+ private $configLoaderMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var ValueFactory|MockObject
*/
- protected $_transFactoryMock;
+ private $dataFactoryMock;
/**
- * @var \Magento\Framework\App\Config\ReinitableConfigInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var StoreManagerInterface|MockObject
*/
- protected $_appConfigMock;
+ private $storeManager;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var Structure|MockObject
*/
- protected $_applicationMock;
+ private $configStructure;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var SettingChecker|MockObject
*/
- protected $_configLoaderMock;
+ private $settingsChecker;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var ScopeResolverPool|MockObject
*/
- protected $_dataFactoryMock;
+ private $scopeResolverPool;
/**
- * @var \Magento\Store\Model\StoreManagerInterface
+ * @var ScopeResolverInterface|MockObject
*/
- protected $_storeManager;
+ private $scopeResolver;
/**
- * @var \Magento\Config\Model\Config\Structure
+ * @var ScopeInterface|MockObject
*/
- protected $_configStructure;
+ private $scope;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var ScopeTypeNormalizer|MockObject
*/
- private $_settingsChecker;
+ private $scopeTypeNormalizer;
protected function setUp()
{
- $this->_eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class);
- $this->_structureReaderMock = $this->createPartialMock(
- \Magento\Config\Model\Config\Structure\Reader::class,
+ $this->eventManagerMock = $this->createMock(ManagerInterface::class);
+ $this->structureReaderMock = $this->createPartialMock(
+ Reader::class,
['getConfiguration']
);
- $this->_configStructure = $this->createMock(\Magento\Config\Model\Config\Structure::class);
+ $this->configStructure = $this->createMock(Structure::class);
- $this->_structureReaderMock->expects(
- $this->any()
- )->method(
- 'getConfiguration'
- )->will(
- $this->returnValue($this->_configStructure)
- );
+ $this->structureReaderMock->method('getConfiguration')
+ ->willReturn($this->configStructure);
- $this->_transFactoryMock = $this->createPartialMock(
- \Magento\Framework\DB\TransactionFactory::class,
+ $this->transFactoryMock = $this->createPartialMock(
+ TransactionFactory::class,
['create', 'addObject']
);
- $this->_appConfigMock = $this->createMock(\Magento\Framework\App\Config\ReinitableConfigInterface::class);
- $this->_configLoaderMock = $this->createPartialMock(
- \Magento\Config\Model\Config\Loader::class,
+ $this->appConfigMock = $this->createMock(ReinitableConfigInterface::class);
+ $this->configLoaderMock = $this->createPartialMock(
+ Loader::class,
['getConfigByPath']
);
- $this->_dataFactoryMock = $this->createMock(\Magento\Framework\App\Config\ValueFactory::class);
-
- $this->_storeManager = $this->getMockForAbstractClass(\Magento\Store\Model\StoreManagerInterface::class);
-
- $this->_settingsChecker = $this
- ->createMock(\Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker::class);
-
- $this->_model = new \Magento\Config\Model\Config(
- $this->_appConfigMock,
- $this->_eventManagerMock,
- $this->_configStructure,
- $this->_transFactoryMock,
- $this->_configLoaderMock,
- $this->_dataFactoryMock,
- $this->_storeManager,
- $this->_settingsChecker
+ $this->dataFactoryMock = $this->createMock(ValueFactory::class);
+
+ $this->storeManager = $this->createMock(StoreManagerInterface::class);
+
+ $this->settingsChecker = $this->createMock(SettingChecker::class);
+
+ $this->scopeResolverPool = $this->createMock(ScopeResolverPool::class);
+ $this->scopeResolver = $this->createMock(ScopeResolverInterface::class);
+ $this->scopeResolverPool->method('get')
+ ->willReturn($this->scopeResolver);
+ $this->scope = $this->createMock(ScopeInterface::class);
+ $this->scopeResolver->method('getScope')
+ ->willReturn($this->scope);
+
+ $this->scopeTypeNormalizer = $this->createMock(ScopeTypeNormalizer::class);
+
+ $this->model = new Config(
+ $this->appConfigMock,
+ $this->eventManagerMock,
+ $this->configStructure,
+ $this->transFactoryMock,
+ $this->configLoaderMock,
+ $this->dataFactoryMock,
+ $this->storeManager,
+ $this->settingsChecker,
+ [],
+ $this->scopeResolverPool,
+ $this->scopeTypeNormalizer
);
}
public function testSaveDoesNotDoAnythingIfGroupsAreNotPassed()
{
- $this->_configLoaderMock->expects($this->never())->method('getConfigByPath');
- $this->_model->save();
+ $this->configLoaderMock->expects($this->never())->method('getConfigByPath');
+ $this->model->save();
}
public function testSaveEmptiesNonSetArguments()
{
- $this->_structureReaderMock->expects($this->never())->method('getConfiguration');
- $this->assertNull($this->_model->getSection());
- $this->assertNull($this->_model->getWebsite());
- $this->assertNull($this->_model->getStore());
- $this->_model->save();
- $this->assertSame('', $this->_model->getSection());
- $this->assertSame('', $this->_model->getWebsite());
- $this->assertSame('', $this->_model->getStore());
+ $this->structureReaderMock->expects($this->never())->method('getConfiguration');
+ $this->assertNull($this->model->getSection());
+ $this->assertNull($this->model->getWebsite());
+ $this->assertNull($this->model->getStore());
+ $this->model->save();
+ $this->assertSame('', $this->model->getSection());
+ $this->assertSame('', $this->model->getWebsite());
+ $this->assertSame('', $this->model->getStore());
}
public function testSaveToCheckAdminSystemConfigChangedSectionEvent()
{
- $transactionMock = $this->createMock(\Magento\Framework\DB\Transaction::class);
+ $transactionMock = $this->createMock(Transaction::class);
- $this->_transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock));
+ $this->transFactoryMock->method('create')
+ ->willReturn($transactionMock);
- $this->_configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([]));
+ $this->configLoaderMock->method('getConfigByPath')
+ ->willReturn([]);
- $this->_eventManagerMock->expects(
+ $this->eventManagerMock->expects(
$this->at(0)
)->method(
'dispatch'
@@ -145,7 +190,7 @@ public function testSaveToCheckAdminSystemConfigChangedSectionEvent()
$this->arrayHasKey('website')
);
- $this->_eventManagerMock->expects(
+ $this->eventManagerMock->expects(
$this->at(0)
)->method(
'dispatch'
@@ -154,123 +199,147 @@ public function testSaveToCheckAdminSystemConfigChangedSectionEvent()
$this->arrayHasKey('store')
);
- $this->_model->setGroups(['1' => ['data']]);
- $this->_model->save();
+ $this->model->setGroups(['1' => ['data']]);
+ $this->model->save();
}
public function testDoNotSaveReadOnlyFields()
{
- $transactionMock = $this->createMock(\Magento\Framework\DB\Transaction::class);
- $this->_transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock));
+ $transactionMock = $this->createMock(Transaction::class);
+ $this->transFactoryMock->method('create')
+ ->willReturn($transactionMock);
- $this->_settingsChecker->expects($this->any())->method('isReadOnly')->will($this->returnValue(true));
- $this->_configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([]));
+ $this->settingsChecker->method('isReadOnly')
+ ->willReturn(true);
+ $this->configLoaderMock->method('getConfigByPath')
+ ->willReturn([]);
- $this->_model->setGroups(['1' => ['fields' => ['key' => ['data']]]]);
- $this->_model->setSection('section');
+ $this->model->setGroups(['1' => ['fields' => ['key' => ['data']]]]);
+ $this->model->setSection('section');
- $group = $this->createMock(\Magento\Config\Model\Config\Structure\Element\Group::class);
- $group->method('getPath')->willReturn('section/1');
+ $group = $this->createMock(Group::class);
+ $group->method('getPath')
+ ->willReturn('section/1');
- $field = $this->createMock(\Magento\Config\Model\Config\Structure\Element\Field::class);
- $field->method('getGroupPath')->willReturn('section/1');
- $field->method('getId')->willReturn('key');
+ $field = $this->createMock(Field::class);
+ $field->method('getGroupPath')
+ ->willReturn('section/1');
+ $field->method('getId')
+ ->willReturn('key');
- $this->_configStructure->expects($this->at(0))
+ $this->configStructure->expects($this->at(0))
->method('getElement')
->with('section/1')
- ->will($this->returnValue($group));
- $this->_configStructure->expects($this->at(1))
+ ->willReturn($group);
+ $this->configStructure->expects($this->at(1))
->method('getElement')
->with('section/1')
- ->will($this->returnValue($group));
- $this->_configStructure->expects($this->at(2))
+ ->willReturn($group);
+ $this->configStructure->expects($this->at(2))
->method('getElement')
->with('section/1/key')
- ->will($this->returnValue($field));
+ ->willReturn($field);
$backendModel = $this->createPartialMock(
- \Magento\Framework\App\Config\Value::class,
+ Value::class,
['addData']
);
- $this->_dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel));
+ $this->dataFactoryMock->method('create')
+ ->willReturn($backendModel);
- $this->_transFactoryMock->expects($this->never())->method('addObject');
- $backendModel->expects($this->never())->method('addData');
+ $this->transFactoryMock->expects($this->never())
+ ->method('addObject');
+ $backendModel->expects($this->never())
+ ->method('addData');
- $this->_model->save();
+ $this->model->save();
}
public function testSaveToCheckScopeDataSet()
{
- $transactionMock = $this->createMock(\Magento\Framework\DB\Transaction::class);
- $this->_transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock));
+ $transactionMock = $this->createMock(Transaction::class);
+ $this->transFactoryMock->method('create')
+ ->willReturn($transactionMock);
- $this->_configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([]));
+ $this->configLoaderMock->method('getConfigByPath')
+ ->willReturn([]);
- $this->_eventManagerMock->expects($this->at(0))
+ $this->eventManagerMock->expects($this->at(0))
->method('dispatch')
->with(
$this->equalTo('admin_system_config_changed_section_section'),
$this->arrayHasKey('website')
);
- $this->_eventManagerMock->expects($this->at(0))
+ $this->eventManagerMock->expects($this->at(0))
->method('dispatch')
->with(
$this->equalTo('admin_system_config_changed_section_section'),
$this->arrayHasKey('store')
);
- $group = $this->createMock(\Magento\Config\Model\Config\Structure\Element\Group::class);
+ $group = $this->createMock(Group::class);
$group->method('getPath')->willReturn('section/1');
- $field = $this->createMock(\Magento\Config\Model\Config\Structure\Element\Field::class);
+ $field = $this->createMock(Field::class);
$field->method('getGroupPath')->willReturn('section/1');
$field->method('getId')->willReturn('key');
- $this->_configStructure->expects($this->at(0))
+ $this->configStructure->expects($this->at(0))
->method('getElement')
->with('section/1')
- ->will($this->returnValue($group));
- $this->_configStructure->expects($this->at(1))
+ ->willReturn($group);
+ $this->configStructure->expects($this->at(1))
->method('getElement')
->with('section/1')
- ->will($this->returnValue($group));
- $this->_configStructure->expects($this->at(2))
+ ->willReturn($group);
+ $this->configStructure->expects($this->at(2))
->method('getElement')
->with('section/1/key')
- ->will($this->returnValue($field));
- $this->_configStructure->expects($this->at(3))
+ ->willReturn($field);
+ $this->configStructure->expects($this->at(3))
->method('getElement')
->with('section/1')
- ->will($this->returnValue($group));
- $this->_configStructure->expects($this->at(4))
+ ->willReturn($group);
+ $this->configStructure->expects($this->at(4))
->method('getElement')
->with('section/1/key')
- ->will($this->returnValue($field));
-
- $website = $this->createMock(\Magento\Store\Model\Website::class);
- $website->expects($this->any())->method('getCode')->will($this->returnValue('website_code'));
- $this->_storeManager->expects($this->any())->method('getWebsite')->will($this->returnValue($website));
- $this->_storeManager->expects($this->any())->method('getWebsites')->will($this->returnValue([$website]));
- $this->_storeManager->expects($this->any())->method('isSingleStoreMode')->will($this->returnValue(true));
-
- $this->_model->setWebsite('website');
- $this->_model->setSection('section');
- $this->_model->setGroups(['1' => ['fields' => ['key' => ['data']]]]);
+ ->willReturn($field);
+
+ $this->scopeResolver->method('getScope')
+ ->with('1')
+ ->willReturn($this->scope);
+ $this->scope->expects($this->atLeastOnce())
+ ->method('getScopeType')
+ ->willReturn('website');
+ $this->scope->expects($this->atLeastOnce())
+ ->method('getId')
+ ->willReturn(1);
+ $this->scope->expects($this->atLeastOnce())
+ ->method('getCode')
+ ->willReturn('website_code');
+ $this->scopeTypeNormalizer->expects($this->atLeastOnce())
+ ->method('normalize')
+ ->with('website')
+ ->willReturn('websites');
+ $website = $this->createMock(Website::class);
+ $this->storeManager->method('getWebsites')->willReturn([$website]);
+ $this->storeManager->method('isSingleStoreMode')->willReturn(true);
+
+ $this->model->setWebsite('1');
+ $this->model->setSection('section');
+ $this->model->setGroups(['1' => ['fields' => ['key' => ['data']]]]);
$backendModel = $this->createPartialMock(
- \Magento\Framework\App\Config\Value::class,
+ Value::class,
['setPath', 'addData', '__sleep', '__wakeup']
);
- $backendModel->expects($this->once())
- ->method('addData')
+ $backendModel->method('addData')
->with([
'field' => 'key',
'groups' => [1 => ['fields' => ['key' => ['data']]]],
'group_id' => null,
'scope' => 'websites',
- 'scope_id' => 0,
+ 'scope_id' => 1,
'scope_code' => 'website_code',
'field_config' => null,
'fieldset_data' => ['key' => null],
@@ -278,18 +347,19 @@ public function testSaveToCheckScopeDataSet()
$backendModel->expects($this->once())
->method('setPath')
->with('section/1/key')
- ->will($this->returnValue($backendModel));
+ ->willReturn($backendModel);
- $this->_dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel));
+ $this->dataFactoryMock->method('create')
+ ->willReturn($backendModel);
- $this->_model->save();
+ $this->model->save();
}
public function testSetDataByPath()
{
$value = 'value';
$path = '//';
- $this->_model->setDataByPath($path, $value);
+ $this->model->setDataByPath($path, $value);
$expected = [
'section' => '',
'groups' => [
@@ -300,7 +370,7 @@ public function testSetDataByPath()
],
],
];
- $this->assertSame($expected, $this->_model->getData());
+ $this->assertSame($expected, $this->model->getData());
}
/**
@@ -309,7 +379,7 @@ public function testSetDataByPath()
*/
public function testSetDataByPathEmpty()
{
- $this->_model->setDataByPath('', 'value');
+ $this->model->setDataByPath('', 'value');
}
/**
@@ -324,7 +394,7 @@ public function testSetDataByPathWrongDepth($path, $expectedException)
$this->expectException('\UnexpectedValueException');
$this->expectExceptionMessage($expectedException);
$value = 'value';
- $this->_model->setDataByPath($path, $value);
+ $this->model->setDataByPath($path, $value);
}
/**
diff --git a/app/code/Magento/Config/etc/di.xml b/app/code/Magento/Config/etc/di.xml
index a5dd18097fb47..87a0e666d2d7b 100644
--- a/app/code/Magento/Config/etc/di.xml
+++ b/app/code/Magento/Config/etc/di.xml
@@ -77,6 +77,11 @@
+
+
+ Magento\Framework\App\Cache\Type\Config
+
+
systemConfigSourceAggregatedProxy
@@ -85,6 +90,7 @@
Magento\Framework\App\Config\PreProcessorComposite
Magento\Framework\Serialize\Serializer\Serialize
Magento\Config\App\Config\Type\System\Reader\Proxy
+ Magento\Framework\Lock\Backend\Cache
diff --git a/app/code/Magento/Config/etc/module.xml b/app/code/Magento/Config/etc/module.xml
index cdf31ab7a5d19..b7df33554af90 100644
--- a/app/code/Magento/Config/etc/module.xml
+++ b/app/code/Magento/Config/etc/module.xml
@@ -6,7 +6,7 @@
*/
-->
-
+
diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Render/TierPriceBox.php b/app/code/Magento/ConfigurableProduct/Pricing/Render/TierPriceBox.php
index 611523a60b06d..816de36b16f96 100644
--- a/app/code/Magento/ConfigurableProduct/Pricing/Render/TierPriceBox.php
+++ b/app/code/Magento/ConfigurableProduct/Pricing/Render/TierPriceBox.php
@@ -5,6 +5,8 @@
*/
namespace Magento\ConfigurableProduct\Pricing\Render;
+use Magento\Catalog\Pricing\Price\TierPrice;
+
/**
* Responsible for displaying tier price box on configurable product page.
*
@@ -17,9 +19,28 @@ class TierPriceBox extends FinalPriceBox
*/
public function toHtml()
{
- // Hide tier price block in case of MSRP.
- if (!$this->isMsrpPriceApplicable()) {
+ // Hide tier price block in case of MSRP or in case when no options with tier price.
+ if (!$this->isMsrpPriceApplicable() && $this->isTierPriceApplicable()) {
return parent::toHtml();
}
}
+
+ /**
+ * Check if at least one of simple products has tier price.
+ *
+ * @return bool
+ */
+ private function isTierPriceApplicable(): bool
+ {
+ $product = $this->getSaleableItem();
+ foreach ($product->getTypeInstance()->getUsedProducts($product) as $simpleProduct) {
+ if ($simpleProduct->isSalable()
+ && !empty($simpleProduct->getPriceInfo()->getPrice(TierPrice::PRICE_CODE)->getTierPriceList())
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml
index d918649ed4914..161441736f940 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml
@@ -69,4 +69,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateConfigurableProductActionGroup.xml
index 3b4e7f55d186c..e16ee43978a1e 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateConfigurableProductActionGroup.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateConfigurableProductActionGroup.xml
@@ -65,4 +65,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml
index 5ae70488d164d..c774dad1ed607 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index be0c2b05e48ba..4f2320666efbc 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
@@ -14,5 +14,6 @@
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckValidatorConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckValidatorConfigurableProductTest.xml
new file mode 100644
index 0000000000000..03f4d8461cebb
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckValidatorConfigurableProductTest.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckingProductQtyAfterOrderCancelTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckingProductQtyAfterOrderCancelTest.xml
new file mode 100644
index 0000000000000..af463c9042357
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckingProductQtyAfterOrderCancelTest.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml
new file mode 100644
index 0000000000000..5daf699294155
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js
index 28e775b984b05..6bbab77a3a0ab 100644
--- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js
+++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js
@@ -53,6 +53,8 @@ define([
if (isConfigurable) {
this.disable();
this.clear();
+ } else {
+ this.enable();
}
}
});
diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js
index e2e0faec3b805..1d251f8ecc333 100644
--- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js
+++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js
@@ -383,7 +383,11 @@ define([
* Chose action for the form save button
*/
saveFormHandler: function () {
- this.serializeData();
+ this.formElement().validate();
+
+ if (this.formElement().source.get('params.invalid') === false) {
+ this.serializeData();
+ }
if (this.checkForNewAttributes()) {
this.formSaveParams = arguments;
diff --git a/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml
index 18f96cfaaf398..325ee1d5d79b3 100644
--- a/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml
+++ b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/tier_price.phtml
@@ -15,10 +15,13 @@
+ ''
+ ''; %>
- <%= $t('Buy %1 for %2 each and').replace('%1', item.qty).replace('%2', priceStr) %>
-
- <%= $t('save') %> <%= item.percentage %>%
-
+ <%= '= $block->escapeHtml(__('Buy %1 for %2 each and', '%1', '%2')) ?>'
+ .replace('%1', item.qty)
+ .replace('%2', priceStr) %>
+
+ = $block->escapeHtml(__('save')) ?> <%= item.percentage %>%
+
<% }); %>
diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js
index 6357bbd6c7c0c..1df84d27a5c30 100644
--- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js
+++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js
@@ -376,7 +376,8 @@ define([
basePrice = parseFloat(this.options.spConfig.prices.basePrice.amount),
optionFinalPrice,
optionPriceDiff,
- optionPrices = this.options.spConfig.optionPrices;
+ optionPrices = this.options.spConfig.optionPrices,
+ allowedProductMinPrice;
this._clearSelect(element);
element.options[0] = new Option('', '');
@@ -407,8 +408,8 @@ define([
if (typeof allowedProducts[0] !== 'undefined' &&
typeof optionPrices[allowedProducts[0]] !== 'undefined') {
-
- optionFinalPrice = parseFloat(optionPrices[allowedProducts[0]].finalPrice.amount);
+ allowedProductMinPrice = this._getAllowedProductWithMinPrice(allowedProducts);
+ optionFinalPrice = parseFloat(optionPrices[allowedProductMinPrice].finalPrice.amount);
optionPriceDiff = optionFinalPrice - basePrice;
if (optionPriceDiff !== 0) {
@@ -489,36 +490,27 @@ define([
_getPrices: function () {
var prices = {},
elements = _.toArray(this.options.settings),
- hasProductPrice = false,
- optionPriceDiff = 0,
- allowedProduct, optionPrices, basePrice, optionFinalPrice;
+ allowedProduct;
_.each(elements, function (element) {
var selected = element.options[element.selectedIndex],
config = selected && selected.config,
priceValue = {};
- if (config && config.allowedProducts.length === 1 && !hasProductPrice) {
- prices = {};
+ if (config && config.allowedProducts.length === 1) {
priceValue = this._calculatePrice(config);
- hasProductPrice = true;
} else if (element.value) {
allowedProduct = this._getAllowedProductWithMinPrice(config.allowedProducts);
- optionPrices = this.options.spConfig.optionPrices;
- basePrice = parseFloat(this.options.spConfig.prices.basePrice.amount);
-
- if (!_.isEmpty(allowedProduct)) {
- optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount);
- optionPriceDiff = optionFinalPrice - basePrice;
- }
-
- if (optionPriceDiff !== 0) {
- prices = {};
- priceValue = this._calculatePriceDifference(allowedProduct);
- }
+ priceValue = this._calculatePrice({
+ 'allowedProducts': [
+ allowedProduct
+ ]
+ });
}
- prices[element.attributeId] = priceValue;
+ if (!_.isEmpty(priceValue)) {
+ prices.prices = priceValue;
+ }
}, this);
return prices;
@@ -539,40 +531,15 @@ define([
_.each(allowedProducts, function (allowedProduct) {
optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount);
- if (_.isEmpty(product)) {
+ if (_.isEmpty(product) || optionFinalPrice < optionMinPrice) {
optionMinPrice = optionFinalPrice;
product = allowedProduct;
}
-
- if (optionFinalPrice < optionMinPrice) {
- product = allowedProduct;
- }
}, this);
return product;
},
- /**
- * Calculate price difference for allowed product
- *
- * @param {*} allowedProduct - Product
- * @returns {*}
- * @private
- */
- _calculatePriceDifference: function (allowedProduct) {
- var displayPrices = $(this.options.priceHolderSelector).priceBox('option').prices,
- newPrices = this.options.spConfig.optionPrices[allowedProduct];
-
- _.each(displayPrices, function (price, code) {
-
- if (newPrices[code]) {
- displayPrices[code].amount = newPrices[code].amount - displayPrices[code].amount;
- }
- });
-
- return displayPrices;
- },
-
/**
* Returns prices for configured products
*
diff --git a/app/code/Magento/Contact/view/frontend/web/css/source/_module.less b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less
new file mode 100644
index 0000000000000..0aaec05aa2afe
--- /dev/null
+++ b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less
@@ -0,0 +1,42 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+*/
+
+& when (@media-common = true) {
+ .contact-index-index {
+ .column:not(.sidebar-main) {
+ .form.contact {
+ float: none;
+ width: 50%;
+ }
+ }
+
+ .column:not(.sidebar-additional) {
+ .form.contact {
+ float: none;
+ width: 50%;
+ }
+ }
+ }
+}
+
+// Mobile
+.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
+ .contact-index-index {
+ .column:not(.sidebar-main) {
+ .form.contact {
+ float: none;
+ width: 100%;
+ }
+ }
+
+ .column:not(.sidebar-additional) {
+ .form.contact {
+ float: none;
+ width: 100%;
+ }
+ }
+ }
+}
+
diff --git a/app/code/Magento/Customer/Block/DataProviders/PostCodesPatternsAttributeData.php b/app/code/Magento/Customer/Block/DataProviders/PostCodesPatternsAttributeData.php
new file mode 100644
index 0000000000000..280948439e1f8
--- /dev/null
+++ b/app/code/Magento/Customer/Block/DataProviders/PostCodesPatternsAttributeData.php
@@ -0,0 +1,50 @@
+postCodeConfig = $postCodeConfig;
+ $this->serializer = $serializer;
+ }
+
+ /**
+ * Get serialized post codes
+ *
+ * @return string
+ */
+ public function getSerializedPostCodes(): string
+ {
+ return $this->serializer->serialize($this->postCodeConfig->getPostCodes());
+ }
+}
diff --git a/app/code/Magento/Customer/Controller/Account/EditPost.php b/app/code/Magento/Customer/Controller/Account/EditPost.php
index da0ad29c5c72f..4d9ec962c292d 100644
--- a/app/code/Magento/Customer/Controller/Account/EditPost.php
+++ b/app/code/Magento/Customer/Controller/Account/EditPost.php
@@ -6,6 +6,8 @@
*/
namespace Magento\Customer\Controller\Account;
+use Magento\Customer\Api\Data\CustomerInterface;
+use Magento\Customer\Model\AddressRegistry;
use Magento\Customer\Model\AuthenticationInterface;
use Magento\Customer\Model\Customer\Mapper;
use Magento\Customer\Model\EmailNotificationInterface;
@@ -23,7 +25,8 @@
use Magento\Framework\Escaper;
/**
- * Class EditPost
+ * Class to editing post.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class EditPost extends \Magento\Customer\Controller\AbstractAccount
@@ -73,9 +76,16 @@ class EditPost extends \Magento\Customer\Controller\AbstractAccount
*/
private $customerMapper;
- /** @var Escaper */
+ /**
+ * @var Escaper
+ */
private $escaper;
+ /**
+ * @var AddressRegistry
+ */
+ private $addressRegistry;
+
/**
* @param Context $context
* @param Session $customerSession
@@ -84,6 +94,7 @@ class EditPost extends \Magento\Customer\Controller\AbstractAccount
* @param Validator $formKeyValidator
* @param CustomerExtractor $customerExtractor
* @param Escaper|null $escaper
+ * @param AddressRegistry|null $addressRegistry
*/
public function __construct(
Context $context,
@@ -92,7 +103,8 @@ public function __construct(
CustomerRepositoryInterface $customerRepository,
Validator $formKeyValidator,
CustomerExtractor $customerExtractor,
- Escaper $escaper = null
+ Escaper $escaper = null,
+ AddressRegistry $addressRegistry = null
) {
parent::__construct($context);
$this->session = $customerSession;
@@ -101,6 +113,7 @@ public function __construct(
$this->formKeyValidator = $formKeyValidator;
$this->customerExtractor = $customerExtractor;
$this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class);
+ $this->addressRegistry = $addressRegistry ?: ObjectManager::getInstance()->get(AddressRegistry::class);
}
/**
@@ -138,7 +151,7 @@ private function getEmailNotification()
}
/**
- * Change customer email or password action
+ * Change customer email or password action.
*
* @return \Magento\Framework\Controller\Result\Redirect
*/
@@ -162,6 +175,9 @@ public function execute()
// whether a customer enabled change password option
$isPasswordChanged = $this->changeCustomerPassword($currentCustomerDataObject->getEmail());
+ // No need to validate customer address while editing customer profile
+ $this->disableAddressValidation($customerCandidateDataObject);
+
$this->customerRepository->save($customerCandidateDataObject);
$this->getEmailNotification()->credentialsChanged(
$customerCandidateDataObject,
@@ -170,6 +186,7 @@ public function execute()
);
$this->dispatchSuccessEvent($customerCandidateDataObject);
$this->messageManager->addSuccess(__('You saved the account information.'));
+
return $resultRedirect->setPath('customer/account');
} catch (InvalidEmailOrPasswordException $e) {
$this->messageManager->addError($e->getMessage());
@@ -180,6 +197,7 @@ public function execute()
$this->session->logout();
$this->session->start();
$this->messageManager->addError($message);
+
return $resultRedirect->setPath('customer/account/login');
} catch (InputException $e) {
$this->messageManager->addErrorMessage($this->escaper->escapeHtml($e->getMessage()));
@@ -313,4 +331,18 @@ private function getCustomerMapper()
}
return $this->customerMapper;
}
+
+ /**
+ * Disable Customer Address Validation.
+ *
+ * @param CustomerInterface $customer
+ * @return void
+ */
+ private function disableAddressValidation(CustomerInterface $customer)
+ {
+ foreach ($customer->getAddresses() as $address) {
+ $addressModel = $this->addressRegistry->retrieve($address->getId());
+ $addressModel->setShouldIgnoreValidation(true);
+ }
+ }
}
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php
index 6753a48d02d6a..1b5160ef31185 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php
@@ -8,12 +8,14 @@
use Magento\Backend\App\Action;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Api\Data\CustomerInterface;
+use Magento\Customer\Model\AddressRegistry;
use Magento\Customer\Model\EmailNotificationInterface;
use Magento\Customer\Ui\Component\Listing\AttributeRepository;
use Magento\Framework\Message\MessageInterface;
+use Magento\Framework\App\ObjectManager;
/**
- * Customer inline edit action
+ * Customer inline edit action.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
@@ -61,6 +63,11 @@ class InlineEdit extends \Magento\Backend\App\Action
*/
private $emailNotification;
+ /**
+ * @var AddressRegistry
+ */
+ private $addressRegistry;
+
/**
* @param Action\Context $context
* @param CustomerRepositoryInterface $customerRepository
@@ -68,6 +75,7 @@ class InlineEdit extends \Magento\Backend\App\Action
* @param \Magento\Customer\Model\Customer\Mapper $customerMapper
* @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
* @param \Psr\Log\LoggerInterface $logger
+ * @param AddressRegistry|null $addressRegistry
*/
public function __construct(
Action\Context $context,
@@ -75,13 +83,15 @@ public function __construct(
\Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
\Magento\Customer\Model\Customer\Mapper $customerMapper,
\Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
- \Psr\Log\LoggerInterface $logger
+ \Psr\Log\LoggerInterface $logger,
+ AddressRegistry $addressRegistry = null
) {
$this->customerRepository = $customerRepository;
$this->resultJsonFactory = $resultJsonFactory;
$this->customerMapper = $customerMapper;
$this->dataObjectHelper = $dataObjectHelper;
$this->logger = $logger;
+ $this->addressRegistry = $addressRegistry ?: ObjectManager::getInstance()->get(AddressRegistry::class);
parent::__construct($context);
}
@@ -210,7 +220,7 @@ protected function updateDefaultBilling(array $data)
}
/**
- * Save customer with error catching
+ * Save customer with error catching.
*
* @param CustomerInterface $customer
* @return void
@@ -218,6 +228,8 @@ protected function updateDefaultBilling(array $data)
protected function saveCustomer(CustomerInterface $customer)
{
try {
+ // No need to validate customer address during inline edit action
+ $this->disableAddressValidation($customer);
$this->customerRepository->save($customer);
} catch (\Magento\Framework\Exception\InputException $e) {
$this->getMessageManager()->addError($this->getErrorWithCustomerId($e->getMessage()));
@@ -303,4 +315,18 @@ protected function getErrorWithCustomerId($errorText)
{
return '[Customer ID: ' . $this->getCustomer()->getId() . '] ' . __($errorText);
}
+
+ /**
+ * Disable Customer Address Validation.
+ *
+ * @param CustomerInterface $customer
+ * @return void
+ */
+ private function disableAddressValidation(CustomerInterface $customer)
+ {
+ foreach ($customer->getAddresses() as $address) {
+ $addressModel = $this->addressRegistry->retrieve($address->getId());
+ $addressModel->setShouldIgnoreValidation(true);
+ }
+ }
}
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php
index 762b872b97b6d..49a51052beb90 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Customer\Controller\Adminhtml\Index;
+use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Backend\App\Action\Context;
use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory;
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
@@ -13,7 +14,7 @@
use Magento\Framework\Controller\ResultFactory;
/**
- * Class MassAssignGroup
+ * Class to execute MassAssignGroup action.
*/
class MassAssignGroup extends AbstractMassAction
{
@@ -39,7 +40,7 @@ public function __construct(
}
/**
- * Customer mass assign group action
+ * Customer mass assign group action.
*
* @param AbstractCollection $collection
* @return \Magento\Backend\Model\View\Result\Redirect
@@ -51,6 +52,8 @@ protected function massAction(AbstractCollection $collection)
// Verify customer exists
$customer = $this->customerRepository->getById($customerId);
$customer->setGroupId($this->getRequest()->getParam('group'));
+ // No need to validate customer and customer address during assigning customer to the group
+ $this->setIgnoreValidationFlag($customer);
$this->customerRepository->save($customer);
$customersUpdated++;
}
@@ -64,4 +67,15 @@ protected function massAction(AbstractCollection $collection)
return $resultRedirect;
}
+
+ /**
+ * Set ignore_validation_flag to skip unnecessary address and customer validation.
+ *
+ * @param CustomerInterface $customer
+ * @return void
+ */
+ private function setIgnoreValidationFlag(CustomerInterface $customer)
+ {
+ $customer->setData('ignore_validation_flag', true);
+ }
}
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
index 12732f81f78a0..3a03e9064a0a3 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
@@ -5,6 +5,15 @@
*/
namespace Magento\Customer\Controller\Adminhtml\Index;
+use Magento\Customer\Api\AccountManagementInterface;
+use Magento\Customer\Api\AddressRepositoryInterface;
+use Magento\Customer\Api\CustomerRepositoryInterface;
+use Magento\Customer\Model\Address\Mapper;
+use Magento\Customer\Model\AddressRegistry;
+use Magento\Framework\Api\DataObjectHelper;
+use Magento\Customer\Api\Data\AddressInterfaceFactory;
+use Magento\Customer\Api\Data\CustomerInterfaceFactory;
+use Magento\Framework\DataObjectFactory as ObjectFactory;
use Magento\Customer\Api\AddressMetadataInterface;
use Magento\Customer\Api\CustomerMetadataInterface;
use Magento\Customer\Api\Data\CustomerInterface;
@@ -12,8 +21,11 @@
use Magento\Customer\Model\EmailNotificationInterface;
use Magento\Customer\Model\Metadata\Form;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\App\ObjectManager;
/**
+ * Class to Save customer.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Save extends \Magento\Customer\Controller\Adminhtml\Index
@@ -23,6 +35,98 @@ class Save extends \Magento\Customer\Controller\Adminhtml\Index
*/
private $emailNotification;
+ /**
+ * @var AddressRegistry
+ */
+ private $addressRegistry;
+
+ /**
+ * @param \Magento\Backend\App\Action\Context $context
+ * @param \Magento\Framework\Registry $coreRegistry
+ * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
+ * @param \Magento\Customer\Model\CustomerFactory $customerFactory
+ * @param \Magento\Customer\Model\AddressFactory $addressFactory
+ * @param \Magento\Customer\Model\Metadata\FormFactory $formFactory
+ * @param \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory
+ * @param \Magento\Customer\Helper\View $viewHelper
+ * @param \Magento\Framework\Math\Random $random
+ * @param CustomerRepositoryInterface $customerRepository
+ * @param \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter
+ * @param Mapper $addressMapper
+ * @param AccountManagementInterface $customerAccountManagement
+ * @param AddressRepositoryInterface $addressRepository
+ * @param CustomerInterfaceFactory $customerDataFactory
+ * @param AddressInterfaceFactory $addressDataFactory
+ * @param \Magento\Customer\Model\Customer\Mapper $customerMapper
+ * @param \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor
+ * @param DataObjectHelper $dataObjectHelper
+ * @param ObjectFactory $objectFactory
+ * @param \Magento\Framework\View\LayoutFactory $layoutFactory
+ * @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory
+ * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
+ * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory
+ * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
+ * @param AddressRegistry|null $addressRegistry
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
+ */
+ public function __construct(
+ \Magento\Backend\App\Action\Context $context,
+ \Magento\Framework\Registry $coreRegistry,
+ \Magento\Framework\App\Response\Http\FileFactory $fileFactory,
+ \Magento\Customer\Model\CustomerFactory $customerFactory,
+ \Magento\Customer\Model\AddressFactory $addressFactory,
+ \Magento\Customer\Model\Metadata\FormFactory $formFactory,
+ \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory,
+ \Magento\Customer\Helper\View $viewHelper,
+ \Magento\Framework\Math\Random $random,
+ CustomerRepositoryInterface $customerRepository,
+ \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter,
+ Mapper $addressMapper,
+ AccountManagementInterface $customerAccountManagement,
+ AddressRepositoryInterface $addressRepository,
+ CustomerInterfaceFactory $customerDataFactory,
+ AddressInterfaceFactory $addressDataFactory,
+ \Magento\Customer\Model\Customer\Mapper $customerMapper,
+ \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor,
+ DataObjectHelper $dataObjectHelper,
+ ObjectFactory $objectFactory,
+ \Magento\Framework\View\LayoutFactory $layoutFactory,
+ \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory,
+ \Magento\Framework\View\Result\PageFactory $resultPageFactory,
+ \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory,
+ \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
+ AddressRegistry $addressRegistry = null
+ ) {
+ parent::__construct(
+ $context,
+ $coreRegistry,
+ $fileFactory,
+ $customerFactory,
+ $addressFactory,
+ $formFactory,
+ $subscriberFactory,
+ $viewHelper,
+ $random,
+ $customerRepository,
+ $extensibleDataObjectConverter,
+ $addressMapper,
+ $customerAccountManagement,
+ $addressRepository,
+ $customerDataFactory,
+ $addressDataFactory,
+ $customerMapper,
+ $dataObjectProcessor,
+ $dataObjectHelper,
+ $objectFactory,
+ $layoutFactory,
+ $resultLayoutFactory,
+ $resultPageFactory,
+ $resultForwardFactory,
+ $resultJsonFactory
+ );
+ $this->addressRegistry = $addressRegistry ?: ObjectManager::getInstance()->get(AddressRegistry::class);
+ }
+
/**
* Reformat customer account data to be compatible with customer service interface
*
@@ -169,7 +273,7 @@ protected function _extractCustomerAddressData(array & $extractedCustomerData)
}
/**
- * Save customer action
+ * Save customer action.
*
* @return \Magento\Backend\Model\View\Result\Redirect
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
@@ -191,6 +295,8 @@ public function execute()
if ($customerId) {
$currentCustomer = $this->_customerRepository->getById($customerId);
+ // No need to validate customer address while editing customer profile
+ $this->disableAddressValidation($currentCustomer);
$customerData = array_merge(
$this->customerMapper->toFlatArray($currentCustomer),
$customerData
@@ -306,6 +412,7 @@ public function execute()
} else {
$resultRedirect->setPath('customer/index');
}
+
return $resultRedirect;
}
@@ -380,4 +487,18 @@ private function getCurrentCustomerId()
return $customerId;
}
+
+ /**
+ * Disable Customer Address Validation
+ *
+ * @param CustomerInterface $customer
+ * @return void
+ */
+ private function disableAddressValidation(CustomerInterface $customer)
+ {
+ foreach ($customer->getAddresses() as $address) {
+ $addressModel = $this->addressRegistry->retrieve($address->getId());
+ $addressModel->setShouldIgnoreValidation(true);
+ }
+ }
}
diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php
index 5ed8ada049b8e..e837517762473 100644
--- a/app/code/Magento/Customer/Model/AccountManagement.php
+++ b/app/code/Magento/Customer/Model/AccountManagement.php
@@ -16,7 +16,9 @@
use Magento\Customer\Model\Config\Share as ConfigShare;
use Magento\Customer\Model\Customer as CustomerModel;
use Magento\Customer\Model\Customer\CredentialsValidator;
+use Magento\Customer\Model\Data\Customer;
use Magento\Customer\Model\Metadata\Validator;
+use Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory;
use Magento\Eav\Model\Validator\Attribute\Backend;
use Magento\Framework\Api\ExtensibleDataObjectConverter;
use Magento\Framework\Api\SearchCriteriaBuilder;
@@ -44,21 +46,21 @@
use Magento\Framework\Phrase;
use Magento\Framework\Reflection\DataObjectProcessor;
use Magento\Framework\Registry;
+use Magento\Framework\Session\SaveHandlerInterface;
+use Magento\Framework\Session\SessionManagerInterface;
use Magento\Framework\Stdlib\DateTime;
use Magento\Framework\Stdlib\StringUtils as StringHelper;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface as PsrLogger;
-use Magento\Framework\Session\SessionManagerInterface;
-use Magento\Framework\Session\SaveHandlerInterface;
-use Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory;
/**
- * Handle various customer account actions
+ * Handle various customer account actions.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class AccountManagement implements AccountManagementInterface
{
@@ -332,6 +334,11 @@ class AccountManagement implements AccountManagementInterface
*/
private $searchCriteriaBuilder;
+ /**
+ * @var AddressRegistry
+ */
+ private $addressRegistry;
+
/**
* @param CustomerFactory $customerFactory
* @param ManagerInterface $eventManager
@@ -359,12 +366,13 @@ class AccountManagement implements AccountManagementInterface
* @param CredentialsValidator|null $credentialsValidator
* @param DateTimeFactory|null $dateTimeFactory
* @param AccountConfirmation|null $accountConfirmation
- * @param DateTimeFactory $dateTimeFactory
* @param SessionManagerInterface|null $sessionManager
* @param SaveHandlerInterface|null $saveHandler
* @param CollectionFactory|null $visitorCollectionFactory
* @param SearchCriteriaBuilder|null $searchCriteriaBuilder
+ * @param AddressRegistry|null $addressRegistry
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function __construct(
CustomerFactory $customerFactory,
@@ -396,7 +404,8 @@ public function __construct(
SessionManagerInterface $sessionManager = null,
SaveHandlerInterface $saveHandler = null,
CollectionFactory $visitorCollectionFactory = null,
- SearchCriteriaBuilder $searchCriteriaBuilder = null
+ SearchCriteriaBuilder $searchCriteriaBuilder = null,
+ AddressRegistry $addressRegistry = null
) {
$this->customerFactory = $customerFactory;
$this->eventManager = $eventManager;
@@ -434,6 +443,8 @@ public function __construct(
?: ObjectManager::getInstance()->get(CollectionFactory::class);
$this->searchCriteriaBuilder = $searchCriteriaBuilder
?: ObjectManager::getInstance()->get(SearchCriteriaBuilder::class);
+ $this->addressRegistry = $addressRegistry
+ ?: ObjectManager::getInstance()->get(AddressRegistry::class);
}
/**
@@ -499,8 +510,11 @@ public function activateById($customerId, $confirmationKey)
* @param \Magento\Customer\Api\Data\CustomerInterface $customer
* @param string $confirmationKey
* @return \Magento\Customer\Api\Data\CustomerInterface
- * @throws \Magento\Framework\Exception\State\InvalidTransitionException
- * @throws \Magento\Framework\Exception\State\InputMismatchException
+ * @throws InputException
+ * @throws InputMismatchException
+ * @throws InvalidTransitionException
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
*/
private function activateCustomer($customer, $confirmationKey)
{
@@ -514,6 +528,8 @@ private function activateCustomer($customer, $confirmationKey)
}
$customer->setConfirmation(null);
+ // No need to validate customer and customer address while activating customer
+ $this->setIgnoreValidationFlag($customer);
$this->customerRepository->save($customer);
$this->getEmailNotification()->newAccount($customer, 'confirmed', '', $this->storeManager->getStore()->getId());
return $customer;
@@ -574,6 +590,9 @@ public function initiatePasswordReset($email, $template, $websiteId = null)
// load customer by email
$customer = $this->customerRepository->get($email, $websiteId);
+ // No need to validate customer address while saving customer reset password token
+ $this->disableAddressValidation($customer);
+
$newPasswordToken = $this->mathRandom->getUniqueHash();
$this->changeResetPasswordLinkToken($customer, $newPasswordToken);
@@ -611,10 +630,10 @@ public function initiatePasswordReset($email, $template, $websiteId = null)
* Match a customer by their RP token.
*
* @param string $rpToken
+ * @return CustomerInterface
* @throws ExpiredException
+ * @throws LocalizedException
* @throws NoSuchEntityException
- *
- * @return CustomerInterface
*/
private function matchCustomerByRpToken(string $rpToken): CustomerInterface
{
@@ -657,6 +676,11 @@ public function resetPassword($email, $resetToken, $newPassword)
} else {
$customer = $this->customerRepository->get($email);
}
+
+ // No need to validate customer and customer address while saving customer reset password token
+ $this->disableAddressValidation($customer);
+ $this->setIgnoreValidationFlag($customer);
+
//Validate Token and new password strength
$this->validateResetPasswordToken($customer->getId(), $resetToken);
$this->credentialsValidator->checkPasswordDifferentFromEmail(
@@ -906,6 +930,8 @@ public function getDefaultShippingAddress($customerId)
* @param string $redirectUrl
* @param array $extensions
* @return void
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
*/
protected function sendEmailConfirmation(CustomerInterface $customer, $redirectUrl, $extensions = [])
{
@@ -960,14 +986,17 @@ public function changePasswordById($customerId, $currentPassword, $newPassword)
}
/**
- * Change customer password
+ * Change customer password.
*
* @param CustomerInterface $customer
* @param string $currentPassword
* @param string $newPassword
* @return bool true on success
* @throws InputException
+ * @throws InputMismatchException
* @throws InvalidEmailOrPasswordException
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
* @throws UserLockedException
*/
private function changePasswordForCustomer($customer, $currentPassword, $newPassword)
@@ -985,6 +1014,7 @@ private function changePasswordForCustomer($customer, $currentPassword, $newPass
$this->checkPasswordStrength($newPassword);
$customerSecure->setPasswordHash($this->createPasswordHash($newPassword));
$this->destroyCustomerSessions($customer->getId());
+ $this->disableAddressValidation($customer);
$this->customerRepository->save($customer);
return true;
@@ -1076,10 +1106,11 @@ public function isCustomerInStore($customerWebsiteId, $storeId)
* @param int $customerId
* @param string $resetPasswordLinkToken
* @return bool
- * @throws \Magento\Framework\Exception\State\InputMismatchException If token is mismatched
- * @throws \Magento\Framework\Exception\State\ExpiredException If token is expired
- * @throws \Magento\Framework\Exception\InputException If token or customer id is invalid
- * @throws \Magento\Framework\Exception\NoSuchEntityException If customer doesn't exist
+ * @throws ExpiredException
+ * @throws InputException
+ * @throws InputMismatchException
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
*/
private function validateResetPasswordToken($customerId, $resetPasswordLinkToken)
{
@@ -1169,6 +1200,8 @@ protected function sendNewAccountEmail(
*
* @param CustomerInterface $customer
* @return $this
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
* @deprecated 100.1.0
*/
protected function sendPasswordResetNotificationEmail($customer)
@@ -1182,6 +1215,7 @@ protected function sendPasswordResetNotificationEmail($customer)
* @param CustomerInterface $customer
* @param int|string|null $defaultStoreId
* @return int
+ * @throws LocalizedException
* @deprecated 100.1.0
*/
protected function getWebsiteStoreId($customer, $defaultStoreId = null)
@@ -1195,6 +1229,8 @@ protected function getWebsiteStoreId($customer, $defaultStoreId = null)
}
/**
+ * Get email template types
+ *
* @return array
* @deprecated 100.1.0
*/
@@ -1228,6 +1264,7 @@ protected function getTemplateTypes()
* @param int|null $storeId
* @param string $email
* @return $this
+ * @throws MailException
* @deprecated 100.1.0
*/
protected function sendEmailTemplate(
@@ -1326,14 +1363,15 @@ public function isResetPasswordLinkTokenExpired($rpToken, $rpTokenCreatedAt)
}
/**
- * Change reset password link token
- *
- * Stores new reset password link token
+ * Set a new reset password link token.
*
* @param CustomerInterface $customer
* @param string $passwordLinkToken
* @return bool
* @throws InputException
+ * @throws InputMismatchException
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
*/
public function changeResetPasswordLinkToken($customer, $passwordLinkToken)
{
@@ -1351,8 +1389,10 @@ public function changeResetPasswordLinkToken($customer, $passwordLinkToken)
$customerSecure->setRpTokenCreatedAt(
$this->dateTimeFactory->create()->format(DateTime::DATETIME_PHP_FORMAT)
);
+ $this->setIgnoreValidationFlag($customer);
$this->customerRepository->save($customer);
}
+
return true;
}
@@ -1361,6 +1401,8 @@ public function changeResetPasswordLinkToken($customer, $passwordLinkToken)
*
* @param CustomerInterface $customer
* @return $this
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
* @deprecated 100.1.0
*/
public function sendPasswordReminderEmail($customer)
@@ -1388,6 +1430,8 @@ public function sendPasswordReminderEmail($customer)
*
* @param CustomerInterface $customer
* @return $this
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
* @deprecated 100.1.0
*/
public function sendPasswordResetConfirmationEmail($customer)
@@ -1432,6 +1476,7 @@ protected function getAddressById(CustomerInterface $customer, $addressId)
*
* @param CustomerInterface $customer
* @return Data\CustomerSecure
+ * @throws NoSuchEntityException
* @deprecated 100.1.0
*/
protected function getFullCustomerObject($customer)
@@ -1457,6 +1502,20 @@ public function getPasswordHash($password)
return $this->encryptor->getHash($password);
}
+ /**
+ * Disable Customer Address Validation
+ *
+ * @param CustomerInterface $customer
+ * @throws NoSuchEntityException
+ */
+ private function disableAddressValidation($customer)
+ {
+ foreach ($customer->getAddresses() as $address) {
+ $addressModel = $this->addressRegistry->retrieve($address->getId());
+ $addressModel->setShouldIgnoreValidation(true);
+ }
+ }
+
/**
* Get email notification
*
@@ -1502,4 +1561,15 @@ private function destroyCustomerSessions($customerId)
$this->saveHandler->destroy($sessionId);
}
}
+
+ /**
+ * Set ignore_validation_flag for reset password flow to skip unnecessary address and customer validation.
+ *
+ * @param Customer $customer
+ * @return void
+ */
+ private function setIgnoreValidationFlag(Customer $customer)
+ {
+ $customer->setData('ignore_validation_flag', true);
+ }
}
diff --git a/app/code/Magento/Customer/Model/Address/AbstractAddress.php b/app/code/Magento/Customer/Model/Address/AbstractAddress.php
index db3436c441ec2..b6e9ba568f2b7 100644
--- a/app/code/Magento/Customer/Model/Address/AbstractAddress.php
+++ b/app/code/Magento/Customer/Model/Address/AbstractAddress.php
@@ -214,7 +214,7 @@ public function getStreet()
}
/**
- * Get steet line by number
+ * Get street line by number
*
* @param int $number
* @return string
diff --git a/app/code/Magento/Customer/Model/Address/AddressModelInterface.php b/app/code/Magento/Customer/Model/Address/AddressModelInterface.php
index 0af36e877555f..06de3a99a831c 100644
--- a/app/code/Magento/Customer/Model/Address/AddressModelInterface.php
+++ b/app/code/Magento/Customer/Model/Address/AddressModelInterface.php
@@ -15,7 +15,7 @@
interface AddressModelInterface
{
/**
- * Get steet line by number
+ * Get street line by number
*
* @param int $number
* @return string
diff --git a/app/code/Magento/Customer/Model/Config/Backend/Address/Street.php b/app/code/Magento/Customer/Model/Config/Backend/Address/Street.php
index fc0fa3ebc073d..40a10a1db0935 100644
--- a/app/code/Magento/Customer/Model/Config/Backend/Address/Street.php
+++ b/app/code/Magento/Customer/Model/Config/Backend/Address/Street.php
@@ -87,15 +87,20 @@ public function afterDelete()
{
$result = parent::afterDelete();
- if ($this->getScope() == \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITES) {
- $attribute = $this->_eavConfig->getAttribute('customer_address', 'street');
- $website = $this->_storeManager->getWebsite($this->getScopeCode());
- $attribute->setWebsite($website);
- $attribute->load($attribute->getId());
- $attribute->setData('scope_multiline_count', null);
- $attribute->save();
- }
+ $attribute = $this->_eavConfig->getAttribute('customer_address', 'street');
+ switch ($this->getScope()) {
+ case \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITES:
+ $website = $this->_storeManager->getWebsite($this->getScopeCode());
+ $attribute->setWebsite($website);
+ $attribute->load($attribute->getId());
+ $attribute->setData('scope_multiline_count', null);
+ break;
+ case ScopeConfigInterface::SCOPE_TYPE_DEFAULT:
+ $attribute->setData('multiline_count', 2);
+ break;
+ }
+ $attribute->save();
return $result;
}
}
diff --git a/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php b/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php
index 5a46fdb9defc4..71f0b393e4a5d 100644
--- a/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php
+++ b/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php
@@ -12,6 +12,7 @@
use Magento\Framework\App\Cache\StateInterface;
use Magento\Framework\App\CacheInterface;
use Magento\Framework\Serialize\SerializerInterface;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Cache for attribute metadata
@@ -53,6 +54,11 @@ class AttributeMetadataCache
*/
private $serializer;
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* Constructor
*
@@ -60,17 +66,21 @@ class AttributeMetadataCache
* @param StateInterface $state
* @param SerializerInterface $serializer
* @param AttributeMetadataHydrator $attributeMetadataHydrator
+ * @param StoreManagerInterface|null $storeManager
*/
public function __construct(
CacheInterface $cache,
StateInterface $state,
SerializerInterface $serializer,
- AttributeMetadataHydrator $attributeMetadataHydrator
+ AttributeMetadataHydrator $attributeMetadataHydrator,
+ StoreManagerInterface $storeManager = null
) {
$this->cache = $cache;
$this->state = $state;
$this->serializer = $serializer;
$this->attributeMetadataHydrator = $attributeMetadataHydrator;
+ $this->storeManager = $storeManager ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(StoreManagerInterface::class);
}
/**
@@ -82,11 +92,12 @@ public function __construct(
*/
public function load($entityType, $suffix = '')
{
- if (isset($this->attributes[$entityType . $suffix])) {
- return $this->attributes[$entityType . $suffix];
+ $storeId = $this->storeManager->getStore()->getId();
+ if (isset($this->attributes[$entityType . $suffix . $storeId])) {
+ return $this->attributes[$entityType . $suffix . $storeId];
}
if ($this->isEnabled()) {
- $cacheKey = self::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix;
+ $cacheKey = self::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix . $storeId;
$serializedData = $this->cache->load($cacheKey);
if ($serializedData) {
$attributesData = $this->serializer->unserialize($serializedData);
@@ -94,7 +105,7 @@ public function load($entityType, $suffix = '')
foreach ($attributesData as $key => $attributeData) {
$attributes[$key] = $this->attributeMetadataHydrator->hydrate($attributeData);
}
- $this->attributes[$entityType . $suffix] = $attributes;
+ $this->attributes[$entityType . $suffix . $storeId] = $attributes;
return $attributes;
}
}
@@ -111,9 +122,10 @@ public function load($entityType, $suffix = '')
*/
public function save($entityType, array $attributes, $suffix = '')
{
- $this->attributes[$entityType . $suffix] = $attributes;
+ $storeId = $this->storeManager->getStore()->getId();
+ $this->attributes[$entityType . $suffix . $storeId] = $attributes;
if ($this->isEnabled()) {
- $cacheKey = self::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix;
+ $cacheKey = self::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix . $storeId;
$attributesData = [];
foreach ($attributes as $key => $attribute) {
$attributesData[$key] = $this->attributeMetadataHydrator->extract($attribute);
diff --git a/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php b/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php
index f28cce0ea2ae1..0e4fc68503122 100644
--- a/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php
+++ b/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php
@@ -1,7 +1,5 @@
setMessage(__('"%1" invalid type entered.', $label), \Zend_Validate_Alnum::INVALID);
$validator->setMessage(
__('"%1" contains non-alphabetic or non-numeric characters.', $label),
diff --git a/app/code/Magento/Customer/Model/ResourceModel/Address.php b/app/code/Magento/Customer/Model/ResourceModel/Address.php
index a52c372310843..8b5c9a08931b5 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/Address.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/Address.php
@@ -13,7 +13,8 @@
use Magento\Framework\App\ObjectManager;
/**
- * Class Address
+ * Customer Address resource model.
+ *
* @package Magento\Customer\Model\ResourceModel
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
@@ -31,8 +32,8 @@ class Address extends \Magento\Eav\Model\Entity\VersionControl\AbstractEntity
/**
* @param \Magento\Eav\Model\Entity\Context $context
- * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot,
- * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationComposite $entityRelationComposite,
+ * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot
+ * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationComposite $entityRelationComposite
* @param \Magento\Framework\Validator\Factory $validatorFactory
* @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository
* @param array $data
@@ -90,7 +91,7 @@ protected function _beforeSave(\Magento\Framework\DataObject $address)
}
/**
- * Validate customer address entity
+ * Validate customer address entity.
*
* @param \Magento\Framework\DataObject $address
* @return void
@@ -98,6 +99,9 @@ protected function _beforeSave(\Magento\Framework\DataObject $address)
*/
protected function _validate($address)
{
+ if ($address->getDataByKey('should_ignore_validation')) {
+ return;
+ };
$validator = $this->_validatorFactory->createValidator('customer_address', 'save');
if (!$validator->isValid($address)) {
diff --git a/app/code/Magento/Customer/Model/ResourceModel/Customer.php b/app/code/Magento/Customer/Model/ResourceModel/Customer.php
index 7e5f9d51549ec..daacb2655f588 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/Customer.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/Customer.php
@@ -11,7 +11,7 @@
use Magento\Framework\Exception\AlreadyExistsException;
/**
- * Customer entity resource model
+ * Customer entity resource model.
*
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -92,7 +92,7 @@ protected function _getDefaultAttributes()
}
/**
- * Check customer scope, email and confirmation key before saving
+ * Check customer scope, email and confirmation key before saving.
*
* @param \Magento\Framework\DataObject $customer
* @return $this
@@ -150,7 +150,9 @@ protected function _beforeSave(\Magento\Framework\DataObject $customer)
$customer->setConfirmation(null);
}
- $this->_validate($customer);
+ if (!$customer->getData('ignore_validation_flag')) {
+ $this->_validate($customer);
+ }
return $this;
}
diff --git a/app/code/Magento/Customer/Model/ResourceModel/Customer/Relation.php b/app/code/Magento/Customer/Model/ResourceModel/Customer/Relation.php
index e55c5d443c9d1..d55a5c0aea2be 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/Customer/Relation.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/Customer/Relation.php
@@ -7,12 +7,12 @@
namespace Magento\Customer\Model\ResourceModel\Customer;
/**
- * Class Relation
+ * Class to process object relations.
*/
class Relation implements \Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationInterface
{
/**
- * Save relations for Customer
+ * Save relations for Customer.
*
* @param \Magento\Framework\Model\AbstractModel $customer
* @return void
@@ -23,41 +23,43 @@ public function processRelation(\Magento\Framework\Model\AbstractModel $customer
$defaultBillingId = $customer->getData('default_billing');
$defaultShippingId = $customer->getData('default_shipping');
- /** @var \Magento\Customer\Model\Address $address */
- foreach ($customer->getAddresses() as $address) {
- if ($address->getData('_deleted')) {
- if ($address->getId() == $defaultBillingId) {
- $customer->setData('default_billing', null);
- }
+ if (!$customer->getData('ignore_validation_flag')) {
+ /** @var \Magento\Customer\Model\Address $address */
+ foreach ($customer->getAddresses() as $address) {
+ if ($address->getData('_deleted')) {
+ if ($address->getId() == $defaultBillingId) {
+ $customer->setData('default_billing', null);
+ }
- if ($address->getId() == $defaultShippingId) {
- $customer->setData('default_shipping', null);
- }
+ if ($address->getId() == $defaultShippingId) {
+ $customer->setData('default_shipping', null);
+ }
- $removedAddressId = $address->getId();
- $address->delete();
+ $removedAddressId = $address->getId();
+ $address->delete();
- // Remove deleted address from customer address collection
- $customer->getAddressesCollection()->removeItemByKey($removedAddressId);
- } else {
- $address->setParentId(
- $customer->getId()
- )->setStoreId(
- $customer->getStoreId()
- )->setIsCustomerSaveTransaction(
- true
- )->save();
+ // Remove deleted address from customer address collection
+ $customer->getAddressesCollection()->removeItemByKey($removedAddressId);
+ } else {
+ $address->setParentId(
+ $customer->getId()
+ )->setStoreId(
+ $customer->getStoreId()
+ )->setIsCustomerSaveTransaction(
+ true
+ )->save();
- if (($address->getIsPrimaryBilling() ||
- $address->getIsDefaultBilling()) && $address->getId() != $defaultBillingId
- ) {
- $customer->setData('default_billing', $address->getId());
- }
+ if (($address->getIsPrimaryBilling() ||
+ $address->getIsDefaultBilling()) && $address->getId() != $defaultBillingId
+ ) {
+ $customer->setData('default_billing', $address->getId());
+ }
- if (($address->getIsPrimaryShipping() ||
- $address->getIsDefaultShipping()) && $address->getId() != $defaultShippingId
- ) {
- $customer->setData('default_shipping', $address->getId());
+ if (($address->getIsPrimaryShipping() ||
+ $address->getIsDefaultShipping()) && $address->getId() != $defaultShippingId
+ ) {
+ $customer->setData('default_shipping', $address->getId());
+ }
}
}
}
diff --git a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
index 9e745769e2c36..d0efdde3d8c59 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
@@ -12,6 +12,7 @@
use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
use Magento\Customer\Model\CustomerFactory;
use Magento\Customer\Model\CustomerRegistry;
+use \Magento\Customer\Model\Customer as CustomerModel;
use Magento\Customer\Model\Data\CustomerSecureFactory;
use Magento\Customer\Model\Customer\NotificationStorage;
use Magento\Customer\Model\Delegation\Data\NewOperation;
@@ -170,7 +171,8 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
@@ -240,7 +242,7 @@ public function save(\Magento\Customer\Api\Data\CustomerInterface $customer, $pa
$prevCustomerDataArr['default_shipping']
);
}
-
+ $this->setIgnoreValidationFlag($customerArr, $customerModel);
$customerModel->save();
$this->customerRegistry->push($customerModel);
$customerId = $customerModel->getId();
@@ -253,7 +255,7 @@ public function save(\Magento\Customer\Api\Data\CustomerInterface $customer, $pa
$delegatedNewOperation->getCustomer()->getAddresses()
);
}
- if ($customer->getAddresses() !== null) {
+ if ($customer->getAddresses() !== null && !$customerModel->getData('ignore_validation_flag')) {
if ($customer->getId()) {
$existingAddresses = $this->getById($customer->getId())->getAddresses();
$getIdFunc = function ($address) {
@@ -365,7 +367,7 @@ public function getList(SearchCriteriaInterface $searchCriteria)
->joinAttribute('billing_telephone', 'customer_address/telephone', 'default_billing', null, 'left')
->joinAttribute('billing_region', 'customer_address/region', 'default_billing', null, 'left')
->joinAttribute('billing_country_id', 'customer_address/country_id', 'default_billing', null, 'left')
- ->joinAttribute('company', 'customer_address/company', 'default_billing', null, 'left');
+ ->joinAttribute('billing_company', 'customer_address/company', 'default_billing', null, 'left');
$this->collectionProcessor->process($searchCriteria, $collection);
@@ -420,4 +422,18 @@ protected function addFilterGroupToCollection(FilterGroup $filterGroup, Collecti
$collection->addFieldToFilter($fields);
}
}
+
+ /**
+ * Set ignore_validation_flag to skip model validation.
+ *
+ * @param array $customerArray
+ * @param CustomerModel $customerModel
+ * @return void
+ */
+ private function setIgnoreValidationFlag(array $customerArray, CustomerModel $customerModel)
+ {
+ if (isset($customerArray['ignore_validation_flag'])) {
+ $customerModel->setData('ignore_validation_flag', true);
+ }
+ }
}
diff --git a/app/code/Magento/Customer/Observer/UpgradeCustomerPasswordObserver.php b/app/code/Magento/Customer/Observer/UpgradeCustomerPasswordObserver.php
index eb7e81009c92c..831506af17cf6 100644
--- a/app/code/Magento/Customer/Observer/UpgradeCustomerPasswordObserver.php
+++ b/app/code/Magento/Customer/Observer/UpgradeCustomerPasswordObserver.php
@@ -6,11 +6,15 @@
namespace Magento\Customer\Observer;
+use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Framework\Encryption\EncryptorInterface;
use Magento\Framework\Event\ObserverInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Model\CustomerRegistry;
+/**
+ * Observer to execute upgrading customer password hash when customer has logged in.
+ */
class UpgradeCustomerPasswordObserver implements ObserverInterface
{
/**
@@ -46,7 +50,7 @@ public function __construct(
}
/**
- * Upgrade customer password hash when customer has logged in
+ * Upgrade customer password hash when customer has logged in.
*
* @param \Magento\Framework\Event\Observer $observer
* @return void
@@ -61,7 +65,20 @@ public function execute(\Magento\Framework\Event\Observer $observer)
if (!$this->encryptor->validateHashVersion($customerSecure->getPasswordHash(), true)) {
$customerSecure->setPasswordHash($this->encryptor->getHash($password, true));
+ // No need to validate customer and customer address while upgrading customer password
+ $this->setIgnoreValidationFlag($customer);
$this->customerRepository->save($customer);
}
}
+
+ /**
+ * Set ignore_validation_flag to skip unnecessary address and customer validation.
+ *
+ * @param CustomerInterface $customer
+ * @return void
+ */
+ private function setIgnoreValidationFlag(CustomerInterface $customer)
+ {
+ $customer->setData('ignore_validation_flag', true);
+ }
}
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml
index 3d6e0fb54b054..3b7aab22f749e 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml
@@ -6,18 +6,17 @@
*/
-->
-
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
+
-
+
-
-
+
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/RemoveCustomerFromAdminActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/RemoveCustomerFromAdminActionGroup.xml
index a53968a920806..3ec06d8353a45 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/RemoveCustomerFromAdminActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/RemoveCustomerFromAdminActionGroup.xml
@@ -10,10 +10,9 @@
-
+
-
@@ -28,6 +27,6 @@
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml
index a8ee604edee0a..d86591b799a33 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml
@@ -9,10 +9,9 @@
xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd">
-
+
-
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
index 4eea31665fe69..573767a12361a 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
@@ -67,6 +67,7 @@
California
90230
US
+ United States
Yes
Yes
RegionCA
@@ -101,6 +102,15 @@
SE1 7RW
GB
+ United Kingdom
444-44-444-44
+
+ false
+ true
+
+
+ true
+ false
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
index 49453a7747d4b..a8b8cece39fad 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
@@ -74,4 +74,16 @@
0
US_Address_NY
+
+ 1
+ John.Doe@example.com
+ John
+ Doe
+ John Doe
+ pwdTest123!
+ 0
+ 0
+ US_Default_Billing_Address_TX
+ US_Default_Shipping_Address_CA
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrdersGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrdersGridSection.xml
index de3975fd82d01..419387fd92d1f 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrdersGridSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrdersGridSection.xml
@@ -1,3 +1,4 @@
+
+
diff --git a/app/code/Magento/Email/Test/Mftf/Section/AdminEmailTemplateEditSection.xml b/app/code/Magento/Email/Test/Mftf/Section/AdminEmailTemplateEditSection.xml
index 9da02b64cb2e7..81c29b28a3fd6 100644
--- a/app/code/Magento/Email/Test/Mftf/Section/AdminEmailTemplateEditSection.xml
+++ b/app/code/Magento/Email/Test/Mftf/Section/AdminEmailTemplateEditSection.xml
@@ -14,5 +14,7 @@
+
+
diff --git a/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php b/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php
index f02849c244cb3..1023dcf552d2f 100644
--- a/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php
+++ b/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php
@@ -64,7 +64,7 @@ public function testGetFinalPrice(
$expectedFinalPrice
) {
$rawFinalPrice = 10;
- $rawPriceCheckStep = 6;
+ $rawPriceCheckStep = 5;
$this->productMock->expects(
$this->any()
@@ -155,7 +155,7 @@ public function getFinalPriceDataProvider()
'custom_option_null' => [
'associatedProducts' => [],
'options' => [[], []],
- 'expectedPriceCall' => 6, /* product call number to check final price formed correctly */
+ 'expectedPriceCall' => 5, /* product call number to check final price formed correctly */
'expectedFinalPrice' => 10, /* 10(product price) + 2(options count) * 5(qty) * 5(option price) */
],
'custom_option_exist' => [
@@ -165,9 +165,9 @@ public function getFinalPriceDataProvider()
['associated_product_2', $optionMock],
['associated_product_3', $optionMock],
],
- 'expectedPriceCall' => 16, /* product call number to check final price formed correctly */
+ 'expectedPriceCall' => 15, /* product call number to check final price formed correctly */
'expectedFinalPrice' => 35, /* 10(product price) + 2(options count) * 5(qty) * 5(option price) */
- ]
+ ],
];
}
diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php b/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php
index 94dc0ee7493b0..7655b0e8929f6 100644
--- a/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php
+++ b/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php
@@ -236,8 +236,8 @@ protected function _getSelectHtmlWithValue(Attribute $attribute, $value)
if ($attribute->getFilterOptions()) {
$options = [];
- foreach ($attribute->getFilterOptions() as $value => $label) {
- $options[] = ['value' => $value, 'label' => $label];
+ foreach ($attribute->getFilterOptions() as $optionValue => $label) {
+ $options[] = ['value' => $optionValue, 'label' => $label];
}
} else {
$options = $attribute->getSource()->getAllOptions(false);
diff --git a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/AdminIndexerActionGroup.xml b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/AdminIndexerActionGroup.xml
new file mode 100644
index 0000000000000..5cf6656f9fb50
--- /dev/null
+++ b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/AdminIndexerActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Indexer/Test/Mftf/Page/AdminIndexManagementPage.xml b/app/code/Magento/Indexer/Test/Mftf/Page/AdminIndexManagementPage.xml
new file mode 100644
index 0000000000000..504608d0721fe
--- /dev/null
+++ b/app/code/Magento/Indexer/Test/Mftf/Page/AdminIndexManagementPage.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml b/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml
new file mode 100644
index 0000000000000..07d93ff850221
--- /dev/null
+++ b/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Msrp/view/base/web/js/msrp.js b/app/code/Magento/Msrp/view/base/web/js/msrp.js
index deeadd9b55b82..72dd1d8bbecbe 100644
--- a/app/code/Magento/Msrp/view/base/web/js/msrp.js
+++ b/app/code/Magento/Msrp/view/base/web/js/msrp.js
@@ -73,6 +73,7 @@ define([
this.initTierPopup();
}
$(this.options.cartButtonId).on('click', this._addToCartSubmit.bind(this));
+ $(this.options.cartForm).on('submit', this._onSubmitForm.bind(this));
},
/**
@@ -249,8 +250,10 @@ define([
/**
* Handler for addToCart action
+ *
+ * @param {Object} e
*/
- _addToCartSubmit: function () {
+ _addToCartSubmit: function (e) {
this.element.trigger('addToCart', this.element);
if (this.element.data('stop-processing')) {
@@ -266,8 +269,20 @@ define([
if (this.options.addToCartUrl) {
$('.mage-dropdown-dialog > .ui-dialog-content').dropdownDialog('close');
}
+
+ e.preventDefault();
$(this.options.cartForm).submit();
+ },
+ /**
+ * Handler for submit form
+ *
+ * @private
+ */
+ _onSubmitForm: function () {
+ if ($(this.options.cartForm).valid()) {
+ $(this.options.cartButtonId).prop('disabled', true);
+ }
}
});
diff --git a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php
index ea7838085ddfb..0ea0abc135f9e 100644
--- a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php
+++ b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php
@@ -1168,7 +1168,7 @@ private function removePlacedItemsFromQuote(array $shippingAddresses, array $pla
{
foreach ($shippingAddresses as $address) {
foreach ($address->getAllItems() as $addressItem) {
- if (in_array($addressItem->getId(), $placedAddressItems)) {
+ if (in_array($addressItem->getQuoteItemId(), $placedAddressItems)) {
if ($addressItem->getProduct()->getIsVirtual()) {
$addressItem->isDeleted(true);
} else {
@@ -1218,7 +1218,7 @@ private function searchQuoteAddressId(OrderInterface $order, array $addresses):
$item = array_pop($items);
foreach ($addresses as $address) {
foreach ($address->getAllItems() as $addressItem) {
- if ($addressItem->getId() == $item->getQuoteItemId()) {
+ if ($addressItem->getQuoteItemId() == $item->getQuoteItemId()) {
return (int)$address->getId();
}
}
diff --git a/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml b/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml
index c6bcdeb7b0413..fee3cb790a522 100644
--- a/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml
+++ b/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml
@@ -12,6 +12,7 @@
Magento\Customer\Block\DataProviders\AddressAttributeData
+ Magento\Customer\Block\DataProviders\PostCodesPatternsAttributeData
diff --git a/app/code/Magento/Newsletter/Controller/Manage/Save.php b/app/code/Magento/Newsletter/Controller/Manage/Save.php
index 419cbac10ffd1..1aa2a4505d518 100644
--- a/app/code/Magento/Newsletter/Controller/Manage/Save.php
+++ b/app/code/Magento/Newsletter/Controller/Manage/Save.php
@@ -8,8 +8,12 @@
namespace Magento\Newsletter\Controller\Manage;
use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository;
+use Magento\Customer\Model\Data\Customer;
use Magento\Newsletter\Model\Subscriber;
+/**
+ * Controller for customer newsletter subscription save.
+ */
class Save extends \Magento\Newsletter\Controller\Manage
{
/**
@@ -58,7 +62,7 @@ public function __construct(
}
/**
- * Save newsletter subscription preference action
+ * Save newsletter subscription preference action.
*
* @return void|null
*/
@@ -81,6 +85,8 @@ public function execute()
$isSubscribedParam = (boolean)$this->getRequest()
->getParam('is_subscribed', false);
if ($isSubscribedParam !== $isSubscribedState) {
+ // No need to validate customer and customer address while saving subscription preferences
+ $this->setIgnoreValidationFlag($customer);
$this->customerRepository->save($customer);
if ($isSubscribedParam) {
$subscribeModel = $this->subscriberFactory->create()
@@ -105,4 +111,15 @@ public function execute()
}
$this->_redirect('customer/account/');
}
+
+ /**
+ * Set ignore_validation_flag to skip unnecessary address and customer validation.
+ *
+ * @param Customer $customer
+ * @return void
+ */
+ private function setIgnoreValidationFlag(Customer $customer)
+ {
+ $customer->setData('ignore_validation_flag', true);
+ }
}
diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php
index 48a89129f3098..2c6f494053efc 100644
--- a/app/code/Magento/Newsletter/Model/Subscriber.php
+++ b/app/code/Magento/Newsletter/Model/Subscriber.php
@@ -552,7 +552,7 @@ public function updateSubscription($customerId)
}
/**
- * Saving customer subscription status
+ * Saving customer subscription status.
*
* @param int $customerId
* @param bool $subscribe indicates whether the customer should be subscribed or unsubscribed
@@ -588,7 +588,12 @@ protected function _updateCustomerSubscription($customerId, $subscribe)
if (AccountManagementInterface::ACCOUNT_CONFIRMATION_REQUIRED
== $this->customerAccountManagement->getConfirmationStatus($customerId)
) {
- $status = self::STATUS_UNCONFIRMED;
+ if ($this->getId() && $this->getStatus() == self::STATUS_SUBSCRIBED) {
+ // if a customer was already subscribed then keep the subscribed
+ $status = self::STATUS_SUBSCRIBED;
+ } else {
+ $status = self::STATUS_UNCONFIRMED;
+ }
} elseif ($isConfirmNeed) {
if ($this->getStatus() != self::STATUS_SUBSCRIBED) {
$status = self::STATUS_NOT_ACTIVE;
diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml
new file mode 100644
index 0000000000000..81b444fb5c1dc
--- /dev/null
+++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Newsletter/Test/Mftf/Page/StorefrontNewsletterManagePage.xml b/app/code/Magento/Newsletter/Test/Mftf/Page/StorefrontNewsletterManagePage.xml
new file mode 100644
index 0000000000000..81fd3eb7c391c
--- /dev/null
+++ b/app/code/Magento/Newsletter/Test/Mftf/Page/StorefrontNewsletterManagePage.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
new file mode 100644
index 0000000000000..36870fbfb0182
--- /dev/null
+++ b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml
new file mode 100644
index 0000000000000..15d6debd7ef25
--- /dev/null
+++ b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml
new file mode 100644
index 0000000000000..96a944a4952ac
--- /dev/null
+++ b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php
index d8c770f02e8a7..0bf6f24a6b989 100644
--- a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php
+++ b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php
@@ -209,6 +209,11 @@ public function testSubscribeNotLoggedIn()
$this->assertEquals(Subscriber::STATUS_NOT_ACTIVE, $this->subscriber->subscribe($email));
}
+ /**
+ * Update status with Confirmation Status - required.
+ *
+ * @return void
+ */
public function testUpdateSubscription()
{
$websiteId = 1;
@@ -225,7 +230,7 @@ public function testUpdateSubscription()
->willReturn(
[
'subscriber_id' => 1,
- 'subscriber_status' => Subscriber::STATUS_SUBSCRIBED
+ 'subscriber_status' => Subscriber::STATUS_SUBSCRIBED,
]
);
$customerDataMock->expects($this->atLeastOnce())->method('getId')->willReturn('id');
@@ -245,8 +250,9 @@ public function testUpdateSubscription()
->getMock();
$this->storeManager->expects($this->any())->method('getStore')->willReturn($storeModel);
$storeModel->expects($this->exactly(2))->method('getWebsiteId')->willReturn($websiteId);
+ $data = $this->subscriber->updateSubscription($customerId);
- $this->assertEquals($this->subscriber, $this->subscriber->updateSubscription($customerId));
+ $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $data->getSubscriberStatus());
}
public function testUnsubscribeCustomerById()
diff --git a/app/code/Magento/Payment/Api/Data/PaymentAdditionalInfoInterface.php b/app/code/Magento/Payment/Api/Data/PaymentAdditionalInfoInterface.php
new file mode 100644
index 0000000000000..c658baece7779
--- /dev/null
+++ b/app/code/Magento/Payment/Api/Data/PaymentAdditionalInfoInterface.php
@@ -0,0 +1,16 @@
+getPaymentMethods() as $code => $data) {
- if (isset($data['title'])) {
- $methods[$code] = $data['title'];
- } else {
- $methods[$code] = $this->getMethodInstance($code)->getConfigData('title', $store);
+ if (!empty($data['active'])) {
+ $storedTitle = $this->getMethodInstance($code)->getConfigData('title', $store);
+ if (!empty($storedTitle)) {
+ $methods[$code] = $storedTitle;
+ } elseif (!empty($data['title'])) {
+ $methods[$code] = $data['title'];
+ }
}
if ($asLabelValue && $withGroups && isset($data['group'])) {
$groupRelations[$code] = $data['group'];
diff --git a/app/code/Magento/Payment/Model/PaymentAdditionalInfo.php b/app/code/Magento/Payment/Model/PaymentAdditionalInfo.php
new file mode 100644
index 0000000000000..4ce41181a008a
--- /dev/null
+++ b/app/code/Magento/Payment/Model/PaymentAdditionalInfo.php
@@ -0,0 +1,60 @@
+key;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setKey($key)
+ {
+ $this->key = $key;
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+ return $this;
+ }
+}
diff --git a/app/code/Magento/Payment/Test/Unit/Helper/DataTest.php b/app/code/Magento/Payment/Test/Unit/Helper/DataTest.php
index 3752e82fd1e5b..1df07f87a3054 100644
--- a/app/code/Magento/Payment/Test/Unit/Helper/DataTest.php
+++ b/app/code/Magento/Payment/Test/Unit/Helper/DataTest.php
@@ -18,6 +18,9 @@ class DataTest extends \PHPUnit\Framework\TestCase
/** @var \PHPUnit_Framework_MockObject_MockObject */
private $scopeConfig;
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
+ private $paymentConfig;
+
/** @var \PHPUnit_Framework_MockObject_MockObject */
private $initialConfig;
@@ -48,6 +51,7 @@ protected function setUp()
$this->methodFactory = $arguments['paymentMethodFactory'];
$this->appEmulation = $arguments['appEmulation'];
+ $this->paymentConfig = $arguments['paymentConfig'];
$this->initialConfig = $arguments['initialConfig'];
$this->helper = $objectManagerHelper->getObject($className, $arguments);
@@ -253,6 +257,56 @@ public function testGetInfoBlockHtml()
$this->assertEquals($blockHtml, $this->helper->getInfoBlockHtml($infoMock, $storeId));
}
+ /**
+ * @param bool $sorted
+ * @param bool $asLabelValue
+ * @param bool $withGroups
+ * @param string|null $configTitle
+ * @param array $paymentMethod
+ * @param array $expectedPaymentMethodList
+ * @return void
+ *
+ * @dataProvider paymentMethodListDataProvider
+ */
+ public function testGetPaymentMethodList(
+ bool $sorted,
+ bool $asLabelValue,
+ bool $withGroups,
+ $configTitle,
+ array $paymentMethod,
+ array $expectedPaymentMethodList
+ ) {
+ $groups = ['group' => 'Group Title'];
+
+ $this->initialConfig->method('getData')
+ ->with('default')
+ ->willReturn(
+ [
+ Data::XML_PATH_PAYMENT_METHODS => [
+ $paymentMethod['code'] => $paymentMethod['data'],
+ ],
+ ]
+ );
+
+ $this->scopeConfig->method('getValue')
+ ->with(sprintf('%s/%s/model', Data::XML_PATH_PAYMENT_METHODS, $paymentMethod['code']))
+ ->willReturn(\Magento\Payment\Model\Method\AbstractMethod::class);
+
+ $methodInstanceMock = $this->getMockBuilder(\Magento\Payment\Model\MethodInterface::class)
+ ->getMockForAbstractClass();
+ $methodInstanceMock->method('getConfigData')
+ ->with('title', null)
+ ->willReturn($configTitle);
+ $this->methodFactory->method('create')
+ ->willReturn($methodInstanceMock);
+
+ $this->paymentConfig->method('getGroups')
+ ->willReturn($groups);
+
+ $paymentMethodList = $this->helper->getPaymentMethodList($sorted, $asLabelValue, $withGroups);
+ $this->assertEquals($expectedPaymentMethodList, $paymentMethodList);
+ }
+
/**
* @return array
*/
@@ -269,4 +323,89 @@ public function getSortMethodsDataProvider()
]
];
}
+
+ /**
+ * @return array
+ */
+ public function paymentMethodListDataProvider(): array
+ {
+ return [
+ 'Payment method with changed title' =>
+ [
+ true,
+ false,
+ false,
+ 'Config Payment Title',
+ [
+ 'code' => 'payment_method',
+ 'data' => [
+ 'active' => 1,
+ 'title' => 'Payment Title',
+ ],
+ ],
+ ['payment_method' => 'Config Payment Title'],
+ ],
+ 'Payment method with default title' =>
+ [
+ true,
+ false,
+ false,
+ null,
+ [
+ 'code' => 'payment_method',
+ 'data' => [
+ 'active' => 1,
+ 'title' => 'Payment Title',
+ ],
+ ],
+ ['payment_method' => 'Payment Title'],
+ ],
+ 'Payment method as value => label' =>
+ [
+ true,
+ true,
+ false,
+ null,
+ [
+ 'code' => 'payment_method',
+ 'data' => [
+ 'active' => 1,
+ 'title' => 'Payment Title',
+ ],
+ ],
+ [
+ 'payment_method' => [
+ 'value' => 'payment_method',
+ 'label' => 'Payment Title',
+ ],
+ ],
+ ],
+ 'Payment method with group' =>
+ [
+ true,
+ true,
+ true,
+ null,
+ [
+ 'code' => 'payment_method',
+ 'data' => [
+ 'active' => 1,
+ 'title' => 'Payment Title',
+ 'group' => 'group',
+ ],
+ ],
+ [
+ 'group' => [
+ 'label' => 'Group Title',
+ 'value' => [
+ 'payment_method' => [
+ 'value' => 'payment_method',
+ 'label' => 'Payment Title',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ];
+ }
}
diff --git a/app/code/Magento/Payment/etc/di.xml b/app/code/Magento/Payment/etc/di.xml
index 74f553cc64094..b7422bb00d543 100644
--- a/app/code/Magento/Payment/etc/di.xml
+++ b/app/code/Magento/Payment/etc/di.xml
@@ -7,6 +7,7 @@
-->
+
diff --git a/app/code/Magento/Paypal/Model/Billing/AbstractAgreement.php b/app/code/Magento/Paypal/Model/Billing/AbstractAgreement.php
index 2ebe088d31d86..8965684d1085f 100644
--- a/app/code/Magento/Paypal/Model/Billing/AbstractAgreement.php
+++ b/app/code/Magento/Paypal/Model/Billing/AbstractAgreement.php
@@ -6,7 +6,7 @@
namespace Magento\Paypal\Model\Billing;
/**
- * Billing Agreement abstaract class
+ * Billing Agreement abstract class
*/
abstract class AbstractAgreement extends \Magento\Framework\Model\AbstractModel
{
diff --git a/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php b/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php
index c2056aea08c00..5d5db0128b1eb 100644
--- a/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php
+++ b/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php
@@ -5,7 +5,6 @@
*/
namespace Magento\Paypal\Model\Config\Structure\Element;
-use Magento\Framework\App\RequestInterface;
use Magento\Config\Model\Config\Structure\Element\Field as FieldConfigStructure;
use Magento\Paypal\Model\Config\StructurePlugin as ConfigStructurePlugin;
@@ -14,19 +13,6 @@
*/
class FieldPlugin
{
- /**
- * @var RequestInterface
- */
- private $request;
-
- /**
- * @param RequestInterface $request
- */
- public function __construct(RequestInterface $request)
- {
- $this->request = $request;
- }
-
/**
* Get original configPath (not changed by PayPal configuration inheritance)
*
@@ -36,7 +22,7 @@ public function __construct(RequestInterface $request)
*/
public function afterGetConfigPath(FieldConfigStructure $subject, $result)
{
- if (!$result && $this->request->getParam('section') == 'payment') {
+ if (!$result && strpos($subject->getPath(), 'payment_') === 0) {
$result = preg_replace(
'@^(' . implode('|', ConfigStructurePlugin::getPaypalConfigCountries(true)) . ')/@',
'payment/',
diff --git a/app/code/Magento/Paypal/Model/Express.php b/app/code/Magento/Paypal/Model/Express.php
index 196e59c6593b9..a44fb9e7c15b0 100644
--- a/app/code/Magento/Paypal/Model/Express.php
+++ b/app/code/Magento/Paypal/Model/Express.php
@@ -44,7 +44,7 @@ class Express extends \Magento\Payment\Model\Method\AbstractMethod
*
* @var bool
*/
- protected $_isGateway = false;
+ protected $_isGateway = true;
/**
* Availability option
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php
index 8615b91383aaa..72c13c80b31e4 100644
--- a/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php
@@ -7,7 +7,6 @@
use Magento\Paypal\Model\Config\Structure\Element\FieldPlugin as FieldConfigStructurePlugin;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
-use Magento\Framework\App\RequestInterface;
use Magento\Config\Model\Config\Structure\Element\Field as FieldConfigStructureMock;
class FieldPluginTest extends \PHPUnit\Framework\TestCase
@@ -22,11 +21,6 @@ class FieldPluginTest extends \PHPUnit\Framework\TestCase
*/
private $objectManagerHelper;
- /**
- * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject
- */
- private $requestMock;
-
/**
* @var FieldConfigStructureMock|\PHPUnit_Framework_MockObject_MockObject
*/
@@ -34,16 +28,13 @@ class FieldPluginTest extends \PHPUnit\Framework\TestCase
protected function setUp()
{
- $this->requestMock = $this->getMockBuilder(RequestInterface::class)
- ->getMockForAbstractClass();
$this->subjectMock = $this->getMockBuilder(FieldConfigStructureMock::class)
->disableOriginalConstructor()
->getMock();
$this->objectManagerHelper = new ObjectManagerHelper($this);
$this->plugin = $this->objectManagerHelper->getObject(
- FieldConfigStructurePlugin::class,
- ['request' => $this->requestMock]
+ FieldConfigStructurePlugin::class
);
}
@@ -56,10 +47,8 @@ public function testAroundGetConfigPathHasResult()
public function testAroundGetConfigPathNonPaymentSection()
{
- $this->requestMock->expects(static::once())
- ->method('getParam')
- ->with('section')
- ->willReturn('non-payment');
+ $this->subjectMock->method('getPath')
+ ->willReturn('non-payment/group/field');
$this->assertNull($this->plugin->afterGetConfigPath($this->subjectMock, null));
}
@@ -72,12 +61,7 @@ public function testAroundGetConfigPathNonPaymentSection()
*/
public function testAroundGetConfigPath($subjectPath, $expectedConfigPath)
{
- $this->requestMock->expects(static::once())
- ->method('getParam')
- ->with('section')
- ->willReturn('payment');
- $this->subjectMock->expects(static::once())
- ->method('getPath')
+ $this->subjectMock->method('getPath')
->willReturn($subjectPath);
$this->assertEquals($expectedConfigPath, $this->plugin->afterGetConfigPath($this->subjectMock, null));
diff --git a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php
index c56e5e3139517..d554b5dd68db2 100644
--- a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php
+++ b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php
@@ -20,6 +20,8 @@ class CreateHandler extends AbstractHandler
const ADDITIONAL_STORE_DATA_KEY = 'additional_store_data';
/**
+ * Execute before Plugin
+ *
* @param \Magento\Catalog\Model\Product\Gallery\CreateHandler $mediaGalleryCreateHandler
* @param \Magento\Catalog\Model\Product $product
* @param array $arguments
@@ -45,6 +47,8 @@ public function beforeExecute(
}
/**
+ * Execute plugin
+ *
* @param \Magento\Catalog\Model\Product\Gallery\CreateHandler $mediaGalleryCreateHandler
* @param \Magento\Catalog\Model\Product $product
* @return \Magento\Catalog\Model\Product
@@ -59,6 +63,9 @@ public function afterExecute(
);
if (!empty($mediaCollection)) {
+ if ($product->getIsDuplicate() === true) {
+ $mediaCollection = $this->makeAllNewVideos($product->getId(), $mediaCollection);
+ }
$newVideoCollection = $this->collectNewVideos($mediaCollection);
$this->saveVideoData($newVideoCollection, 0);
@@ -71,6 +78,8 @@ public function afterExecute(
}
/**
+ * Saves video data
+ *
* @param array $videoDataCollection
* @param int $storeId
* @return void
@@ -84,6 +93,8 @@ protected function saveVideoData(array $videoDataCollection, $storeId)
}
/**
+ * Saves additioanal video data
+ *
* @param array $videoDataCollection
* @return void
*/
@@ -100,6 +111,8 @@ protected function saveAdditionalStoreData(array $videoDataCollection)
}
/**
+ * Saves video data
+ *
* @param array $item
* @return void
*/
@@ -112,6 +125,8 @@ protected function saveVideoValuesItem(array $item)
}
/**
+ * Excludes current store data
+ *
* @param array $mediaCollection
* @param int $currentStoreId
* @return array
@@ -127,6 +142,8 @@ function ($item) use ($currentStoreId) {
}
/**
+ * Prepare video data for saving
+ *
* @param array $rowData
* @return array
*/
@@ -144,6 +161,8 @@ protected function prepareVideoRowDataForSave(array $rowData)
}
/**
+ * Loads video data
+ *
* @param array $mediaCollection
* @param int $excludedStore
* @return array
@@ -166,6 +185,8 @@ protected function loadStoreViewVideoData(array $mediaCollection, $excludedStore
}
/**
+ * Collect video data
+ *
* @param array $mediaCollection
* @return array
*/
@@ -183,6 +204,8 @@ protected function collectVideoData(array $mediaCollection)
}
/**
+ * Extract video data
+ *
* @param array $rowData
* @return array
*/
@@ -195,6 +218,8 @@ protected function extractVideoDataFromRowData(array $rowData)
}
/**
+ * Collect items for additional data adding
+ *
* @param array $mediaCollection
* @return array
*/
@@ -210,6 +235,8 @@ protected function collectVideoEntriesIdsToAdditionalLoad(array $mediaCollection
}
/**
+ * Add additional data
+ *
* @param array $mediaCollection
* @param array $data
* @return array
@@ -230,6 +257,8 @@ protected function addAdditionalStoreData(array $mediaCollection, array $data):
}
/**
+ * Creates additional video data
+ *
* @param array $storeData
* @param int $valueId
* @return array
@@ -248,6 +277,8 @@ protected function createAdditionalStoreDataCollection(array $storeData, $valueI
}
/**
+ * Collect new videos
+ *
* @param array $mediaCollection
* @return array
*/
@@ -263,6 +294,8 @@ private function collectNewVideos(array $mediaCollection): array
}
/**
+ * Checks if gallery item is video
+ *
* @param $item
* @return bool
*/
@@ -274,6 +307,8 @@ private function isVideoItem($item): bool
}
/**
+ * Checks if video is new
+ *
* @param $item
* @return bool
*/
@@ -283,4 +318,23 @@ private function isNewVideo($item): bool
|| empty($item['video_url_default'])
|| empty($item['video_title_default']);
}
+
+ /**
+ * Mark all videos as new
+ *
+ * @param int $entityId
+ * @param array $mediaCollection
+ * @return array
+ */
+ private function makeAllNewVideos($entityId, array $mediaCollection): array
+ {
+ foreach ($mediaCollection as $key => $video) {
+ if ($this->isVideoItem($video)) {
+ unset($video['video_url_default'], $video['video_title_default']);
+ $video['entity_id'] = $entityId;
+ $mediaCollection[$key] = $video;
+ }
+ }
+ return $mediaCollection;
+ }
}
diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js
index 3966321f6072c..b7f4adb857a91 100644
--- a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js
+++ b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js
@@ -179,12 +179,14 @@ define([
* @private
*/
clearEvents: function () {
- this.fotoramaItem.off(
- 'fotorama:show.' + this.PV +
- ' fotorama:showend.' + this.PV +
- ' fotorama:fullscreenenter.' + this.PV +
- ' fotorama:fullscreenexit.' + this.PV
- );
+ if (this.fotoramaItem !== undefined) {
+ this.fotoramaItem.off(
+ 'fotorama:show.' + this.PV +
+ ' fotorama:showend.' + this.PV +
+ ' fotorama:fullscreenenter.' + this.PV +
+ ' fotorama:fullscreenexit.' + this.PV
+ );
+ }
},
/**
diff --git a/app/code/Magento/Quote/Api/Data/CartInterface.php b/app/code/Magento/Quote/Api/Data/CartInterface.php
index 551833e3effb1..b87869de6b3df 100644
--- a/app/code/Magento/Quote/Api/Data/CartInterface.php
+++ b/app/code/Magento/Quote/Api/Data/CartInterface.php
@@ -223,14 +223,14 @@ public function setBillingAddress(\Magento\Quote\Api\Data\AddressInterface $bill
/**
* Returns the reserved order ID for the cart.
*
- * @return int|null Reserved order ID. Otherwise, null.
+ * @return string|null Reserved order ID. Otherwise, null.
*/
public function getReservedOrderId();
/**
* Sets the reserved order ID for the cart.
*
- * @param int $reservedOrderId
+ * @param string $reservedOrderId
* @return $this
*/
public function setReservedOrderId($reservedOrderId);
diff --git a/app/code/Magento/Quote/Model/Quote.php b/app/code/Magento/Quote/Model/Quote.php
index 640f89844546e..a3f5d1aaa6a6a 100644
--- a/app/code/Magento/Quote/Model/Quote.php
+++ b/app/code/Magento/Quote/Model/Quote.php
@@ -2218,6 +2218,11 @@ public function validateMinimumAmount($multishipping = false)
if (!$minOrderActive) {
return true;
}
+ $includeDiscount = $this->_scopeConfig->getValue(
+ 'sales/minimum_order/include_discount_amount',
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
+ $storeId
+ );
$minOrderMulti = $this->_scopeConfig->isSetFlag(
'sales/minimum_order/multi_address',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE,
@@ -2251,7 +2256,10 @@ public function validateMinimumAmount($multishipping = false)
$taxes = ($taxInclude) ? $address->getBaseTaxAmount() : 0;
foreach ($address->getQuote()->getItemsCollection() as $item) {
/** @var \Magento\Quote\Model\Quote\Item $item */
- $amount = $item->getBaseRowTotal() - $item->getBaseDiscountAmount() + $taxes;
+ $amount = $includeDiscount ?
+ $item->getBaseRowTotal() - $item->getBaseDiscountAmount() + $taxes :
+ $item->getBaseRowTotal() + $taxes;
+
if ($amount < $minAmount) {
return false;
}
@@ -2261,7 +2269,9 @@ public function validateMinimumAmount($multishipping = false)
$baseTotal = 0;
foreach ($addresses as $address) {
$taxes = ($taxInclude) ? $address->getBaseTaxAmount() : 0;
- $baseTotal += $address->getBaseSubtotalWithDiscount() + $taxes;
+ $baseTotal += $includeDiscount ?
+ $address->getBaseSubtotalWithDiscount() + $taxes :
+ $address->getBaseSubtotal() + $taxes;
}
if ($baseTotal < $minAmount) {
return false;
diff --git a/app/code/Magento/Quote/Model/Quote/Address.php b/app/code/Magento/Quote/Model/Quote/Address.php
index f0419c7f96095..38a97783ca012 100644
--- a/app/code/Magento/Quote/Model/Quote/Address.php
+++ b/app/code/Magento/Quote/Model/Quote/Address.php
@@ -1150,6 +1150,11 @@ public function validateMinimumAmount()
return true;
}
+ $includeDiscount = $this->_scopeConfig->getValue(
+ 'sales/minimum_order/include_discount_amount',
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
+ $storeId
+ );
$amount = $this->_scopeConfig->getValue(
'sales/minimum_order/amount',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE,
@@ -1160,9 +1165,12 @@ public function validateMinimumAmount()
\Magento\Store\Model\ScopeInterface::SCOPE_STORE,
$storeId
);
+
$taxes = $taxInclude ? $this->getBaseTaxAmount() : 0;
- return ($this->getBaseSubtotalWithDiscount() + $taxes >= $amount);
+ return $includeDiscount ?
+ ($this->getBaseSubtotalWithDiscount() + $taxes >= $amount) :
+ ($this->getBaseSubtotal() + $taxes >= $amount);
}
/**
diff --git a/app/code/Magento/Quote/Model/Quote/Address/Total.php b/app/code/Magento/Quote/Model/Quote/Address/Total.php
index 42224c970ed27..00060c15c10d8 100644
--- a/app/code/Magento/Quote/Model/Quote/Address/Total.php
+++ b/app/code/Magento/Quote/Model/Quote/Address/Total.php
@@ -6,6 +6,8 @@
namespace Magento\Quote\Model\Quote\Address;
/**
+ * Class Total
+ *
* @method string getCode()
*
* @api
@@ -54,6 +56,8 @@ public function __construct(
*/
public function setTotalAmount($code, $amount)
{
+ $amount = is_float($amount) ? round($amount, 4) : $amount;
+
$this->totalAmounts[$code] = $amount;
if ($code != 'subtotal') {
$code = $code . '_amount';
@@ -72,6 +76,8 @@ public function setTotalAmount($code, $amount)
*/
public function setBaseTotalAmount($code, $amount)
{
+ $amount = is_float($amount) ? round($amount, 4) : $amount;
+
$this->baseTotalAmounts[$code] = $amount;
if ($code != 'subtotal') {
$code = $code . '_amount';
@@ -167,6 +173,7 @@ public function getAllBaseTotalAmounts()
/**
* Set the full info, which is used to capture tax related information.
+ *
* If a string is used, it is assumed to be serialized.
*
* @param array|string $info
diff --git a/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php b/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php
index 84f1fc1c35adf..e9a63dad6e169 100644
--- a/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php
+++ b/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php
@@ -9,6 +9,9 @@
use Magento\Quote\Api\Data\AddressInterface;
use Magento\Quote\Model\Quote\Address\FreeShippingInterface;
+/**
+ * Collect totals for shipping.
+ */
class Shipping extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal
{
/**
@@ -111,7 +114,7 @@ public function fetch(\Magento\Quote\Model\Quote $quote, \Magento\Quote\Model\Qu
{
$amount = $total->getShippingAmount();
$shippingDescription = $total->getShippingDescription();
- $title = ($amount != 0 && $shippingDescription)
+ $title = ($shippingDescription)
? __('Shipping & Handling (%1)', $shippingDescription)
: __('Shipping & Handling');
@@ -227,7 +230,7 @@ private function getAssignmentWeightData(AddressInterface $address, array $items
* @param bool $addressFreeShipping
* @param float $itemWeight
* @param float $itemQty
- * @param $freeShipping
+ * @param bool $freeShipping
* @return float
*/
private function getItemRowWeight(
diff --git a/app/code/Magento/Quote/Model/Quote/Item/ToOrderItem.php b/app/code/Magento/Quote/Model/Quote/Item/ToOrderItem.php
index 32687499274f8..6192d3471ccb0 100644
--- a/app/code/Magento/Quote/Model/Quote/Item/ToOrderItem.php
+++ b/app/code/Magento/Quote/Model/Quote/Item/ToOrderItem.php
@@ -48,6 +48,8 @@ public function __construct(
}
/**
+ * Convert quote item(quote address item) into order item.
+ *
* @param Item|AddressItem $item
* @param array $data
* @return OrderItemInterface
@@ -63,6 +65,16 @@ public function convert($item, $data = [])
'to_order_item',
$item
);
+ if ($item instanceof \Magento\Quote\Model\Quote\Address\Item) {
+ $orderItemData = array_merge(
+ $orderItemData,
+ $this->objectCopyService->getDataFromFieldset(
+ 'quote_convert_address_item',
+ 'to_order_item',
+ $item
+ )
+ );
+ }
if (!$item->getNoDiscount()) {
$data = array_merge(
$data,
diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php
index e25b770b7a81e..8784310d540bd 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php
@@ -216,6 +216,7 @@ public function testValidateMiniumumAmountVirtual()
$scopeConfigValues = [
['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true],
['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20],
+ ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, true],
['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true],
];
@@ -240,6 +241,31 @@ public function testValidateMiniumumAmount()
$scopeConfigValues = [
['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true],
['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20],
+ ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, true],
+ ['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true],
+ ];
+
+ $this->quote->expects($this->once())
+ ->method('getStoreId')
+ ->willReturn($storeId);
+ $this->quote->expects($this->once())
+ ->method('getIsVirtual')
+ ->willReturn(false);
+
+ $this->scopeConfig->expects($this->once())
+ ->method('isSetFlag')
+ ->willReturnMap($scopeConfigValues);
+
+ $this->assertTrue($this->address->validateMinimumAmount());
+ }
+
+ public function testValidateMiniumumAmountWithoutDiscount()
+ {
+ $storeId = 1;
+ $scopeConfigValues = [
+ ['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true],
+ ['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20],
+ ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, false],
['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true],
];
@@ -263,6 +289,7 @@ public function testValidateMiniumumAmountNegative()
$scopeConfigValues = [
['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true],
['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20],
+ ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, true],
['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true],
];
diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php
index 6f5e5937a32c8..9e921f744642f 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php
@@ -951,6 +951,7 @@ public function testValidateMiniumumAmount()
['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true],
['sales/minimum_order/multi_address', ScopeInterface::SCOPE_STORE, $storeId, true],
['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20],
+ ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, true],
['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true],
];
$this->scopeConfig->expects($this->any())
@@ -977,6 +978,7 @@ public function testValidateMiniumumAmountNegative()
['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true],
['sales/minimum_order/multi_address', ScopeInterface::SCOPE_STORE, $storeId, true],
['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20],
+ ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, true],
['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true],
];
$this->scopeConfig->expects($this->any())
diff --git a/app/code/Magento/Quote/etc/fieldset.xml b/app/code/Magento/Quote/etc/fieldset.xml
index 55ec76a647fcd..85ee20c7f8520 100644
--- a/app/code/Magento/Quote/etc/fieldset.xml
+++ b/app/code/Magento/Quote/etc/fieldset.xml
@@ -186,6 +186,11 @@
+
diff --git a/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml
index d7c5c19d4d813..6fcf5b0c82b4f 100644
--- a/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml
+++ b/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml
@@ -9,7 +9,7 @@
-
+
- .review .action.submit
@@ -18,8 +18,8 @@
-
-
+
+
diff --git a/app/code/Magento/Review/view/frontend/layout/checkout_cart_configure.xml b/app/code/Magento/Review/view/frontend/layout/checkout_cart_configure.xml
index 0a7ddd8b8903d..8a853cdd2e409 100644
--- a/app/code/Magento/Review/view/frontend/layout/checkout_cart_configure.xml
+++ b/app/code/Magento/Review/view/frontend/layout/checkout_cart_configure.xml
@@ -9,7 +9,7 @@
-
+
-
diff --git a/app/code/Magento/Review/view/frontend/layout/customer_account.xml b/app/code/Magento/Review/view/frontend/layout/customer_account.xml
index 54d171cbf1322..9f759dba41782 100644
--- a/app/code/Magento/Review/view/frontend/layout/customer_account.xml
+++ b/app/code/Magento/Review/view/frontend/layout/customer_account.xml
@@ -8,7 +8,7 @@
-
+
review/customer
My Product Reviews
diff --git a/app/code/Magento/Review/view/frontend/layout/customer_account_index.xml b/app/code/Magento/Review/view/frontend/layout/customer_account_index.xml
index 73174f0570e28..2e898a539a954 100644
--- a/app/code/Magento/Review/view/frontend/layout/customer_account_index.xml
+++ b/app/code/Magento/Review/view/frontend/layout/customer_account_index.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/app/code/Magento/Review/view/frontend/layout/review_customer_index.xml b/app/code/Magento/Review/view/frontend/layout/review_customer_index.xml
index 2857e859aa06c..b5f7562963314 100644
--- a/app/code/Magento/Review/view/frontend/layout/review_customer_index.xml
+++ b/app/code/Magento/Review/view/frontend/layout/review_customer_index.xml
@@ -9,7 +9,7 @@
-
+
diff --git a/app/code/Magento/Review/view/frontend/layout/review_customer_view.xml b/app/code/Magento/Review/view/frontend/layout/review_customer_view.xml
index d51c89a1abe1a..d3adbd7950cf9 100644
--- a/app/code/Magento/Review/view/frontend/layout/review_customer_view.xml
+++ b/app/code/Magento/Review/view/frontend/layout/review_customer_view.xml
@@ -9,7 +9,7 @@
-
+
diff --git a/app/code/Magento/Review/view/frontend/layout/review_product_list.xml b/app/code/Magento/Review/view/frontend/layout/review_product_list.xml
index c83cfe95d7964..8c5c1297cdda3 100644
--- a/app/code/Magento/Review/view/frontend/layout/review_product_list.xml
+++ b/app/code/Magento/Review/view/frontend/layout/review_product_list.xml
@@ -9,15 +9,15 @@
-
+
-
+
-
-
+
+
diff --git a/app/code/Magento/Review/view/frontend/layout/review_product_listajax.xml b/app/code/Magento/Review/view/frontend/layout/review_product_listajax.xml
index af8d2dc2f506f..36fa71ea5125a 100644
--- a/app/code/Magento/Review/view/frontend/layout/review_product_listajax.xml
+++ b/app/code/Magento/Review/view/frontend/layout/review_product_listajax.xml
@@ -7,8 +7,8 @@
-->
-
-
-getDisplayIfEmpty()): ?>
+isReviewEnabled() && $block->getDisplayIfEmpty()): ?>