diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/Media.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/Media.php
index de40210ebc76f..b444d746f8d0f 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/Media.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/Media.php
@@ -6,15 +6,36 @@
namespace Magento\CatalogImportExport\Model\Import\Product\Validator;
use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Url\Validator;
class Media extends AbstractImportValidator implements RowValidatorInterface
{
+ /**
+ * @deprecated As this regexp doesn't give guarantee of correct url validation
+ * @see \Magento\Framework\Url\Validator::isValid()
+ */
const URL_REGEXP = '|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i';
const PATH_REGEXP = '#^(?!.*[\\/]\.{2}[\\/])(?!\.{2}[\\/])[-\w.\\/]+$#';
const ADDITIONAL_IMAGES = 'additional_images';
+ /**
+ * The url validator. Checks if given url is valid.
+ *
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * @param Validator $validator The url validator
+ */
+ public function __construct(Validator $validator = null)
+ {
+ $this->validator = $validator ?: ObjectManager::getInstance()->get(Validator::class);
+ }
+
/**
* @deprecated
* @see \Magento\CatalogImportExport\Model\Import\Product::getMultipleValueSeparator()
@@ -27,6 +48,8 @@ class Media extends AbstractImportValidator implements RowValidatorInterface
/**
* @param string $string
* @return bool
+ * @deprecated As this method doesn't give guarantee of correct url validation.
+ * @see \Magento\Framework\Url\Validator::isValid() It provides better url validation.
*/
protected function checkValidUrl($string)
{
@@ -64,7 +87,7 @@ public function isValid($value)
$valid = true;
foreach ($this->mediaAttributes as $attribute) {
if (isset($value[$attribute]) && strlen($value[$attribute])) {
- if (!$this->checkPath($value[$attribute]) && !$this->checkValidUrl($value[$attribute])) {
+ if (!$this->checkPath($value[$attribute]) && !$this->validator->isValid($value[$attribute])) {
$this->_addMessages(
[
sprintf(
@@ -79,7 +102,7 @@ public function isValid($value)
}
if (isset($value[self::ADDITIONAL_IMAGES]) && strlen($value[self::ADDITIONAL_IMAGES])) {
foreach (explode($this->context->getMultipleValueSeparator(), $value[self::ADDITIONAL_IMAGES]) as $image) {
- if (!$this->checkPath($image) && !$this->checkValidUrl($image)) {
+ if (!$this->checkPath($image) && !$this->validator->isValid($image)) {
$this->_addMessages(
[
sprintf(
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Validator/MediaTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Validator/MediaTest.php
index ba3f1f77954e7..209d999320906 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Validator/MediaTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Validator/MediaTest.php
@@ -10,6 +10,8 @@
use Magento\CatalogImportExport\Model\Import\Product\Validator\Media;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use Magento\ImportExport\Model\Import;
+use Magento\Framework\Url\Validator;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
class MediaTest extends \PHPUnit_Framework_TestCase
{
@@ -19,16 +21,35 @@ class MediaTest extends \PHPUnit_Framework_TestCase
/** @var ObjectManagerHelper */
protected $objectManagerHelper;
+ /**
+ * @var Validator|MockObject
+ */
+ private $validatorMock;
+
protected function setUp()
{
-
+ $this->validatorMock = $this->getMockBuilder(Validator::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $contextMock = $this->getMockBuilder(Product::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $contextMock->expects($this->any())
+ ->method('getMultipleValueSeparator')
+ ->willReturn(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR);
+ $contextMock->expects($this->any())
+ ->method('retrieveMessageTemplate')
+ ->with(Media::ERROR_INVALID_MEDIA_URL_OR_PATH)
+ ->willReturn('%s');
+
$this->objectManagerHelper = new ObjectManagerHelper($this);
$this->media = $this->objectManagerHelper->getObject(
Media::class,
[
-
+ 'validator' => $this->validatorMock
]
);
+ $this->media->init($contextMock);
}
public function testInit()
@@ -44,17 +65,8 @@ public function testInit()
*/
public function testIsValid($data, $expected)
{
- $contextMock = $this->getMockBuilder(Product::class)
- ->disableOriginalConstructor()
- ->getMock();
- $contextMock->expects($this->any())
- ->method('getMultipleValueSeparator')
- ->willReturn(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR);
- $contextMock->expects($this->any())
- ->method('retrieveMessageTemplate')
- ->with(Media::ERROR_INVALID_MEDIA_URL_OR_PATH)
- ->willReturn('%s');
- $this->media->init($contextMock);
+ $this->validatorMock->expects($this->never())
+ ->method('isValid');
$result = $this->media->isValid($data);
$this->assertEquals($expected['result'], $result);
@@ -76,6 +88,47 @@ public function testIsValidClearMessagesCall()
$media->isValid([]);
}
+ /**
+ * @param array $data
+ * @param array $expected
+ * @dataProvider isValidAdditionalImagesPathDataProvider
+ */
+ public function testIsValidAdditionalImagesPath($data, $expected)
+ {
+ if ($expected['result']) {
+ $this->validatorMock->expects($this->never())
+ ->method('isValid');
+ } else {
+ $this->validatorMock->expects($this->once())
+ ->method('isValid')
+ ->with($data['additional_images'])
+ ->willReturn(false);
+ }
+
+ $result = $this->media->isValid($data);
+ $this->assertEquals($expected['result'], $result);
+ $messages = $this->media->getMessages();
+ $this->assertEquals($expected['messages'], $messages);
+ }
+
+ /**
+ * @param array $data
+ * @param array $expected
+ * @dataProvider isValidAdditionalImagesUrlDataProvider
+ */
+ public function testIsValidAdditionalImagesUrl($data, $expected)
+ {
+ $this->validatorMock->expects($this->once())
+ ->method('isValid')
+ ->with($data['additional_images'])
+ ->willReturn($expected['result']);
+
+ $result = $this->media->isValid($data);
+ $this->assertEquals($expected['result'], $result);
+ $messages = $this->media->getMessages();
+ $this->assertEquals($expected['messages'], $messages);
+ }
+
/**
* @return array
*/
@@ -94,6 +147,15 @@ public function isMediaValidDataProvider()
['_media_image' => 1],
['result' => true,'messages' => []],
],
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function isValidAdditionalImagesPathDataProvider()
+ {
+ return [
'additional_images' => [
['additional_images' => 'image1.png,image2.jpg'],
['result' => true, 'messages' => []]
@@ -101,6 +163,23 @@ public function isMediaValidDataProvider()
'additional_images_fail' => [
['additional_images' => 'image1.png|image2.jpg|image3.gif'],
['result' => false, 'messages' => [0 => 'additional_images']]
+ ],
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function isValidAdditionalImagesUrlDataProvider()
+ {
+ return [
+ 'additional_images_wrong_domain' => [
+ ['additional_images' => 'https://example/images/some-name.jpg'],
+ ['result' => false, 'messages' => [0 => 'additional_images']],
+ ],
+ 'additional_images_url_multiple_underscores' => [
+ ['additional_images' => 'https://example.com/images/some-name__with___multiple____underscores.jpg'],
+ ['result' => true, 'messages' => []]
]
];
}
diff --git a/app/code/Magento/Config/App/Config/Type/System.php b/app/code/Magento/Config/App/Config/Type/System.php
index a10462d87b205..9208d2bf2f816 100644
--- a/app/code/Magento/Config/App/Config/Type/System.php
+++ b/app/code/Magento/Config/App/Config/Type/System.php
@@ -6,10 +6,11 @@
namespace Magento\Config\App\Config\Type;
use Magento\Framework\App\Config\ConfigTypeInterface;
-use Magento\Framework\DataObject;
+use Magento\Framework\App\ObjectManager;
+use Magento\Config\App\Config\Type\System\Reader;
/**
- * Class process source, cache them and retrieve value by path
+ * System configuration type
*/
class System implements ConfigTypeInterface
{
@@ -23,9 +24,9 @@ class System implements ConfigTypeInterface
private $source;
/**
- * @var DataObject
+ * @var array
*/
- private $data;
+ private $data = [];
/**
* @var \Magento\Framework\App\Config\Spi\PostProcessorInterface
@@ -65,14 +66,18 @@ class System implements ConfigTypeInterface
private $configType;
/**
- * Key name for flag which displays whether configuration is cached or not.
+ * @var Reader
+ */
+ private $reader;
+
+ /**
+ * List of scopes that were retrieved from configuration storage
*
- * Once configuration is cached additional flag pushed to cache storage
- * to be able check cache existence without data load.
+ * Is used to make sure that we don't try to load non-existing configuration scopes.
*
- * @var string
+ * @var array
*/
- private $cacheExistenceKey;
+ private $availableDataScopes = null;
/**
* @param \Magento\Framework\App\Config\ConfigSourceInterface $source
@@ -83,6 +88,7 @@ class System implements ConfigTypeInterface
* @param \Magento\Framework\App\Config\Spi\PreProcessorInterface $preProcessor
* @param int $cachingNestedLevel
* @param string $configType
+ * @param Reader $reader
*/
public function __construct(
\Magento\Framework\App\Config\ConfigSourceInterface $source,
@@ -92,7 +98,8 @@ public function __construct(
\Magento\Framework\Serialize\SerializerInterface $serializer,
\Magento\Framework\App\Config\Spi\PreProcessorInterface $preProcessor,
$cachingNestedLevel = 1,
- $configType = self::CONFIG_TYPE
+ $configType = self::CONFIG_TYPE,
+ Reader $reader = null
) {
$this->source = $source;
$this->postProcessor = $postProcessor;
@@ -102,150 +109,174 @@ public function __construct(
$this->fallback = $fallback;
$this->serializer = $serializer;
$this->configType = $configType;
- $this->cacheExistenceKey = $this->configType . '_CACHE_EXISTS';
+ $this->reader = $reader ?: ObjectManager::getInstance()->get(Reader::class);
}
/**
+ * System configuration is separated by scopes (default, websites, stores). Configuration of a scope is inherited
+ * from its parent scope (store inherits website).
+ *
+ * Because there can be many scopes on single instance of application, the configuration data can be pretty large,
+ * so it does not make sense to load all of it on every application request. That is why we cache configuration
+ * data by scope and only load configuration scope when a value from that scope is requested.
+ *
+ * Possible path values:
+ * '' - will return whole system configuration (default scope + all other scopes)
+ * 'default' - will return all default scope configuration values
+ * '{scopeType}' - will return data from all scopes of a specified {scopeType} (websites, stores)
+ * '{scopeType}/{scopeCode}' - will return data for all values of the scope specified by {scopeCode} and scope type
+ * '{scopeType}/{scopeCode}/some/config/variable' - will return value of the config variable in the specified scope
+ *
* @inheritdoc
*/
public function get($path = '')
{
- if ($path === null) {
- $path = '';
+ if ($path === '') {
+ $this->data = array_replace_recursive($this->loadAllData(), $this->data);
+ return $this->data;
}
- if ($this->isConfigRead($path)) {
- return $this->data->getData($path);
+ $pathParts = explode('/', $path);
+ if (count($pathParts) === 1 && $pathParts[0] !== 'default') {
+ if (!isset($this->data[$pathParts[0]])) {
+ $data = $this->reader->read();
+ $this->data = array_replace_recursive($data, $this->data);
+ }
+ return $this->data[$pathParts[0]];
}
-
- if (!empty($path) && $this->isCacheExists()) {
- return $this->readFromCache($path);
+ $scopeType = array_shift($pathParts);
+ if ($scopeType === 'default') {
+ if (!isset($this->data[$scopeType])) {
+ $this->data = array_replace_recursive($this->loadDefaultScopeData($scopeType), $this->data);
+ }
+ return $this->getDataByPathParts($this->data[$scopeType], $pathParts);
}
-
- $config = $this->loadConfig();
- $this->cacheConfig($config);
- $this->data = new DataObject($config);
- return $this->data->getData($path);
- }
-
- /**
- * Check whether configuration is cached
- *
- * In case configuration cache exists method 'load' returns
- * value equal to $this->cacheExistenceKey
- *
- * @return bool
- */
- private function isCacheExists()
- {
- return $this->cache->load($this->cacheExistenceKey) !== false;
+ $scopeId = array_shift($pathParts);
+ if (!isset($this->data[$scopeType][$scopeId])) {
+ $this->data = array_replace_recursive($this->loadScopeData($scopeType, $scopeId), $this->data);
+ }
+ return isset($this->data[$scopeType][$scopeId])
+ ? $this->getDataByPathParts($this->data[$scopeType][$scopeId], $pathParts)
+ : null;
}
/**
- * Explode path by '/'(forward slash) separator
- *
- * In case $path string contains forward slash symbol(/) the $path is exploded and parts array is returned
- * In other case empty array is returned
+ * Load configuration data for all scopes
*
- * @param string $path
* @return array
*/
- private function getPathParts($path)
+ private function loadAllData()
{
- $pathParts = [];
- if (strpos($path, '/') !== false) {
- $pathParts = explode('/', $path);
+ $cachedData = $this->cache->load($this->configType);
+ if ($cachedData === false) {
+ $data = $this->reader->read();
+ } else {
+ $data = $this->serializer->unserialize($cachedData);
}
- return $pathParts;
+ return $data;
}
/**
- * Check whether requested configuration data is read to memory
- *
- * Because of configuration is cached partially each part can be loaded separately
- * Method performs check if corresponding system configuration part is already loaded to memory
- * and value can be retrieved directly without cache look up
+ * Load configuration data for default scope
*
- *
- * @param string $path
- * @return bool
+ * @param string $scopeType
+ * @return array
*/
- private function isConfigRead($path)
+ private function loadDefaultScopeData($scopeType)
{
- $pathParts = $this->getPathParts($path);
- return !empty($pathParts) && isset($this->data[$pathParts[0]][$pathParts[1]]);
+ $cachedData = $this->cache->load($this->configType . '_' . $scopeType);
+ if ($cachedData === false) {
+ $data = $this->reader->read();
+ $this->cacheData($data);
+ } else {
+ $data = [$scopeType => $this->serializer->unserialize($cachedData)];
+ }
+ return $data;
}
/**
- * Load configuration from all the sources
- *
- * System configuration is loaded in 3 steps performing consecutive calls to
- * Pre Processor, Fallback Processor, Post Processor accordingly
+ * Load configuration data for a specified scope
*
+ * @param string $scopeType
+ * @param string $scopeId
* @return array
*/
- private function loadConfig()
+ private function loadScopeData($scopeType, $scopeId)
{
- $data = $this->preProcessor->process($this->source->get());
- $this->data = new DataObject($data);
- $data = $this->fallback->process($data);
- $this->data = new DataObject($data);
-
- return $this->postProcessor->process($data);
+ $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 => []]];
+ }
+ $data = $this->reader->read();
+ $this->cacheData($data);
+ } else {
+ $data = [$scopeType => [$scopeId => $this->serializer->unserialize($cachedData)]];
+ }
+ return $data;
}
/**
- *
- * Load configuration and caching it by parts.
- *
- * To be cached configuration is loaded first.
- * Then it is cached by parts to minimize memory usage on load.
- * Additional flag cached as well to give possibility check cache existence without data load.
+ * Cache configuration data.
+ * Caches data per scope to avoid reading data for all scopes on every request
*
* @param array $data
* @return void
*/
- private function cacheConfig($data)
+ private function cacheData(array $data)
{
- foreach ($data as $scope => $scopeData) {
- foreach ($scopeData as $key => $config) {
+ $this->cache->save(
+ $this->serializer->serialize($data),
+ $this->configType,
+ [self::CACHE_TAG]
+ );
+ $this->cache->save(
+ $this->serializer->serialize($data['default']),
+ $this->configType . '_default',
+ [self::CACHE_TAG]
+ );
+ $scopes = [];
+ foreach (['websites', 'stores'] as $curScopeType) {
+ foreach ($data[$curScopeType] as $curScopeId => $curScopeData) {
+ $scopes[$curScopeType][$curScopeId] = 1;
$this->cache->save(
- $this->serializer->serialize($config),
- $this->configType . '_' . $scope . $key,
+ $this->serializer->serialize($curScopeData),
+ $this->configType . '_' . $curScopeType . '_' . $curScopeId,
[self::CACHE_TAG]
);
}
}
- $this->cache->save($this->cacheExistenceKey, $this->cacheExistenceKey, [self::CACHE_TAG]);
+ $this->cache->save(
+ $this->serializer->serialize($scopes),
+ $this->configType . "_scopes",
+ [self::CACHE_TAG]
+ );
}
/**
- * Read cached configuration
+ * Walk nested hash map by keys from $pathParts
*
- * Read section of system configuration corresponding to requested $path from cache
- * Configuration stored to internal property right after load to prevent additional
- * requests to cache storage
- *
- * @param string $path
+ * @param array $data to walk in
+ * @param array $pathParts keys path
* @return mixed
*/
- private function readFromCache($path)
+ private function getDataByPathParts($data, $pathParts)
{
- if ($this->data === null) {
- $this->data = new DataObject();
- }
-
- $result = null;
- $pathParts = $this->getPathParts($path);
- if (!empty($pathParts)) {
- $result = $this->cache->load($this->configType . '_' . $pathParts[0] . $pathParts[1]);
- if ($result !== false) {
- $readData = $this->data->getData();
- $readData[$pathParts[0]][$pathParts[1]] = $this->serializer->unserialize($result);
- $this->data->setData($readData);
+ foreach ($pathParts as $key) {
+ if ((array)$data === $data && isset($data[$key])) {
+ $data = $data[$key];
+ } elseif ($data instanceof \Magento\Framework\DataObject) {
+ $data = $data->getDataByKey($key);
+ } else {
+ return null;
}
}
-
- return $this->data->getData($path);
+ return $data;
}
/**
@@ -259,7 +290,7 @@ private function readFromCache($path)
*/
public function clean()
{
- $this->data = null;
+ $this->data = [];
$this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]);
}
}
diff --git a/app/code/Magento/Config/App/Config/Type/System/Reader.php b/app/code/Magento/Config/App/Config/Type/System/Reader.php
new file mode 100644
index 0000000000000..d5a0f09cdd631
--- /dev/null
+++ b/app/code/Magento/Config/App/Config/Type/System/Reader.php
@@ -0,0 +1,71 @@
+source = $source;
+ $this->fallback = $fallback;
+ $this->preProcessor = $preProcessor;
+ $this->postProcessor = $postProcessor;
+ }
+
+ /**
+ * Retrieve and process system configuration data
+ *
+ * Processing includes configuration fallback (default, website, store) and placeholder replacement
+ *
+ * @return array
+ */
+ public function read()
+ {
+ return $this->postProcessor->process(
+ $this->fallback->process(
+ $this->preProcessor->process(
+ $this->source->get()
+ )
+ )
+ );
+ }
+}
diff --git a/app/code/Magento/Config/Model/Config/Importer.php b/app/code/Magento/Config/Model/Config/Importer.php
index 58b667b9ef465..58fc46d35747e 100644
--- a/app/code/Magento/Config/Model/Config/Importer.php
+++ b/app/code/Magento/Config/Model/Config/Importer.php
@@ -13,8 +13,7 @@
use Magento\Framework\App\State;
use Magento\Framework\Config\ScopeInterface;
use Magento\Framework\Exception\State\InvalidTransitionException;
-use Magento\Framework\Flag\FlagResource;
-use Magento\Framework\FlagFactory;
+use Magento\Framework\FlagManager;
use Magento\Framework\Stdlib\ArrayUtils;
/**
@@ -32,18 +31,11 @@ class Importer implements ImporterInterface
const FLAG_CODE = 'system_config_snapshot';
/**
- * The flag factory.
+ * The flag manager.
*
- * @var FlagFactory
+ * @var FlagManager
*/
- private $flagFactory;
-
- /**
- * The flag resource.
- *
- * @var FlagResource
- */
- private $flagResource;
+ private $flagManager;
/**
* An array utils.
@@ -81,8 +73,7 @@ class Importer implements ImporterInterface
private $saveProcessor;
/**
- * @param FlagFactory $flagFactory The flag factory
- * @param FlagResource $flagResource The flag resource
+ * @param FlagManager $flagManager The flag manager
* @param ArrayUtils $arrayUtils An array utils
* @param SaveProcessor $saveProcessor Saves configuration data
* @param ScopeConfigInterface $scopeConfig The application config storage.
@@ -90,16 +81,14 @@ class Importer implements ImporterInterface
* @param ScopeInterface $scope The application scope
*/
public function __construct(
- FlagFactory $flagFactory,
- FlagResource $flagResource,
+ FlagManager $flagManager,
ArrayUtils $arrayUtils,
SaveProcessor $saveProcessor,
ScopeConfigInterface $scopeConfig,
State $state,
ScopeInterface $scope
) {
- $this->flagFactory = $flagFactory;
- $this->flagResource = $flagResource;
+ $this->flagManager = $flagManager;
$this->arrayUtils = $arrayUtils;
$this->saveProcessor = $saveProcessor;
$this->scopeConfig = $scopeConfig;
@@ -118,13 +107,10 @@ public function import(array $data)
$currentScope = $this->scope->getCurrentScope();
try {
- $flag = $this->flagFactory->create(['data' => ['flag_code' => static::FLAG_CODE]]);
- $this->flagResource->load($flag, static::FLAG_CODE, 'flag_code');
- $previouslyProcessedData = $flag->getFlagData() ?: [];
-
+ $savedFlag = $this->flagManager->getFlagData(static::FLAG_CODE) ?: [];
$changedData = array_replace_recursive(
- $this->arrayUtils->recursiveDiff($previouslyProcessedData, $data),
- $this->arrayUtils->recursiveDiff($data, $previouslyProcessedData)
+ $this->arrayUtils->recursiveDiff($savedFlag, $data),
+ $this->arrayUtils->recursiveDiff($data, $savedFlag)
);
/**
@@ -142,8 +128,7 @@ public function import(array $data)
$this->saveProcessor->process($changedData);
});
- $flag->setFlagData($data);
- $this->flagResource->save($flag);
+ $this->flagManager->saveFlag(static::FLAG_CODE, $data);
} catch (\Exception $e) {
throw new InvalidTransitionException(__('%1', $e->getMessage()), $e);
} finally {
diff --git a/app/code/Magento/Config/Model/PreparedValueFactory.php b/app/code/Magento/Config/Model/PreparedValueFactory.php
index 842b1dff002bc..dc10bdd767bef 100644
--- a/app/code/Magento/Config/Model/PreparedValueFactory.php
+++ b/app/code/Magento/Config/Model/PreparedValueFactory.php
@@ -80,7 +80,6 @@ public function __construct(
* @return ValueInterface
* @throws RuntimeException If Value can not be created
* @see ValueInterface
- * @see Value
*/
public function create($path, $value, $scope, $scopeCode = null)
{
diff --git a/app/code/Magento/Config/Test/Unit/App/Config/Type/System/ReaderTest.php b/app/code/Magento/Config/Test/Unit/App/Config/Type/System/ReaderTest.php
new file mode 100644
index 0000000000000..3f02e22465aa0
--- /dev/null
+++ b/app/code/Magento/Config/Test/Unit/App/Config/Type/System/ReaderTest.php
@@ -0,0 +1,93 @@
+source = $this->getMockBuilder(ConfigSourceInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $this->fallback = $this->getMockBuilder(Fallback::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->preProcessor = $this->getMockBuilder(PreProcessorInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $this->postProcessor = $this->getMockBuilder(PostProcessorInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->model = $helper->getObject(
+ Reader::class,
+ [
+ 'source' => $this->source,
+ 'fallback' => $this->fallback,
+ 'preProcessor' => $this->preProcessor,
+ 'postProcessor' => $this->postProcessor
+ ]
+ );
+ }
+
+ public function testGetCachedWithLoadDefaultScopeData()
+ {
+ $data = [
+ 'default' => [],
+ 'websites' => [],
+ 'stores' => []
+ ];
+ $this->source->expects($this->once())
+ ->method('get')
+ ->willReturn($data);
+ $this->preProcessor->expects($this->once())
+ ->method('process')
+ ->with($data)
+ ->willReturn($data);
+ $this->fallback->expects($this->once())
+ ->method('process')
+ ->with($data)
+ ->willReturn($data);
+ $this->postProcessor->expects($this->once())
+ ->method('process')
+ ->with($data)
+ ->willReturn($data);
+ $this->assertEquals($data, $this->model->read());
+ }
+}
diff --git a/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php b/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php
index 985ef34fe0238..cbc89df802890 100644
--- a/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php
+++ b/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php
@@ -13,6 +13,7 @@
use Magento\Framework\Cache\FrontendInterface;
use Magento\Framework\Serialize\SerializerInterface;
use Magento\Store\Model\Config\Processor\Fallback;
+use Magento\Config\App\Config\Type\System\Reader;
/**
* Test how Class process source, cache them and retrieve value by path
@@ -55,6 +56,11 @@ class SystemTest extends \PHPUnit_Framework_TestCase
*/
private $serializer;
+ /**
+ * @var Reader|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $reader;
+
public function setUp()
{
$this->source = $this->getMockBuilder(ConfigSourceInterface::class)
@@ -70,6 +76,9 @@ public function setUp()
->getMockForAbstractClass();
$this->serializer = $this->getMockBuilder(SerializerInterface::class)
->getMock();
+ $this->reader = $this->getMockBuilder(Reader::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->configType = new System(
$this->source,
@@ -77,23 +86,27 @@ public function setUp()
$this->fallback,
$this->cache,
$this->serializer,
- $this->preProcessor
+ $this->preProcessor,
+ 1,
+ 'system',
+ $this->reader
);
}
- public function testGetCached()
+ public function testGetCachedWithLoadDefaultScopeData()
{
$path = 'default/dev/unsecure/url';
$url = 'http://magento.test/';
$data = [
- 'unsecure' => [
- 'url' => $url
+ 'dev' => [
+ 'unsecure' => [
+ 'url' => $url
+ ]
]
];
$this->cache->expects($this->any())
->method('load')
- ->withConsecutive(['system_CACHE_EXISTS'], ['system_defaultdev'])
->willReturnOnConsecutiveCalls('1', serialize($data));
$this->serializer->expects($this->once())
->method('unserialize')
@@ -101,62 +114,62 @@ public function testGetCached()
$this->assertEquals($url, $this->configType->get($path));
}
- public function testGetNotCached()
+ public function testGetCachedWithLoadAllData()
{
- $path = 'default/dev/unsecure/url';
$url = 'http://magento.test/';
$data = [
- 'default' => [
- 'dev' => [
- 'unsecure' => [
- 'url' => $url
- ]
+ 'dev' => [
+ 'unsecure' => [
+ 'url' => $url
]
]
];
+ $this->cache->expects($this->any())
+ ->method('load')
+ ->willReturnOnConsecutiveCalls('1', serialize($data));
+ $this->serializer->expects($this->once())
+ ->method('unserialize')
+ ->willReturn($data);
+ $this->assertEquals($data, $this->configType->get(''));
+ }
+
+ public function testGetNotCached()
+ {
+ $path = 'stores/default/dev/unsecure/url';
+ $url = 'http://magento.test/';
+
$dataToCache = [
'unsecure' => [
'url' => $url
]
];
+ $data = [
+ 'default' => [],
+ 'websites' => [],
+ 'stores' => [
+ 'default' => [
+ 'dev' => [
+ 'unsecure' => [
+ 'url' => $url
+ ]
+ ]
+ ]
+ ]
+ ];
$this->cache->expects($this->any())
->method('load')
- ->withConsecutive(['system_CACHE_EXISTS'], ['system_defaultdev'])
->willReturnOnConsecutiveCalls(false, false);
- $this->serializer->expects($this->once())
+ $this->serializer->expects($this->atLeastOnce())
->method('serialize')
->willReturn(serialize($dataToCache));
- $this->source->expects($this->once())
- ->method('get')
- ->willReturn($data);
- $this->fallback->expects($this->once())
- ->method('process')
- ->with($data)
- ->willReturnArgument(0);
- $this->preProcessor->expects($this->once())
- ->method('process')
- ->with($data)
- ->willReturnArgument(0);
- $this->postProcessor->expects($this->once())
- ->method('process')
- ->with($data)
- ->willReturnArgument(0);
- $this->cache->expects($this->any())
+ $this->cache->expects($this->atLeastOnce())
->method('save')
- ->withConsecutive(
- [
- serialize($dataToCache),
- 'system_defaultdev',
- [System::CACHE_TAG]
- ],
- [
- 'system_CACHE_EXISTS',
- 'system_CACHE_EXISTS',
- [System::CACHE_TAG]
- ]
- );
+ ->willReturnSelf();
+ $this->reader->expects($this->once())
+ ->method('read')
+ ->willReturn($data);
$this->assertEquals($url, $this->configType->get($path));
}
diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/ImporterTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/ImporterTest.php
index a8504e567142f..8e9ae506e7fb3 100644
--- a/app/code/Magento/Config/Test/Unit/Model/Config/ImporterTest.php
+++ b/app/code/Magento/Config/Test/Unit/Model/Config/ImporterTest.php
@@ -15,7 +15,7 @@
use Magento\Framework\Config\ScopeInterface;
use Magento\Framework\Flag;
use Magento\Framework\Flag\FlagResource;
-use Magento\Framework\FlagFactory;
+use Magento\Framework\FlagManager;
use Magento\Framework\Stdlib\ArrayUtils;
use PHPUnit_Framework_MockObject_MockObject as Mock;
@@ -33,20 +33,15 @@ class ImporterTest extends \PHPUnit_Framework_TestCase
private $model;
/**
- * @var FlagFactory|Mock
+ * @var FlagManager|Mock
*/
- private $flagFactoryMock;
+ private $flagManagerMock;
/**
* @var Flag|Mock
*/
private $flagMock;
- /**
- * @var FlagResource|Mock
- */
- private $flagResourceMock;
-
/**
* @var ArrayUtils|Mock
*/
@@ -87,7 +82,7 @@ class ImporterTest extends \PHPUnit_Framework_TestCase
*/
protected function setUp()
{
- $this->flagFactoryMock = $this->getMockBuilder(FlagFactory::class)
+ $this->flagManagerMock = $this->getMockBuilder(FlagManager::class)
->disableOriginalConstructor()
->getMock();
$this->flagMock = $this->getMockBuilder(Flag::class)
@@ -116,13 +111,12 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
- $this->flagFactoryMock->expects($this->any())
+ $this->flagManagerMock->expects($this->any())
->method('create')
->willReturn($this->flagMock);
$this->model = new Importer(
- $this->flagFactoryMock,
- $this->flagResourceMock,
+ $this->flagManagerMock,
$this->arrayUtilsMock,
$this->saveProcessorMock,
$this->scopeConfigMock,
@@ -131,16 +125,17 @@ protected function setUp()
);
}
+ /**
+ * @SuppressWarnings(PHPMD.UnusedLocalVariable)
+ */
public function testImport()
{
$data = [];
$currentData = ['current' => '2'];
- $this->flagResourceMock->expects($this->once())
- ->method('load')
- ->with($this->flagMock, Importer::FLAG_CODE, 'flag_code');
- $this->flagMock->expects($this->once())
+ $this->flagManagerMock->expects($this->once())
->method('getFlagData')
+ ->with(Importer::FLAG_CODE)
->willReturn($currentData);
$this->arrayUtilsMock->expects($this->exactly(2))
->method('recursiveDiff')
@@ -153,16 +148,22 @@ public function testImport()
->willReturn('oldScope');
$this->stateMock->expects($this->once())
->method('emulateAreaCode')
- ->with(Area::AREA_ADMINHTML, $this->anything());
- $this->scopeMock->expects($this->once())
+ ->with(Area::AREA_ADMINHTML, $this->anything())
+ ->willReturnCallback(function ($area, $function) {
+ return $function();
+ });
+ $this->saveProcessorMock->expects($this->once())
+ ->method('process')
+ ->with([]);
+ $this->scopeMock->expects($this->at(1))
+ ->method('setCurrentScope')
+ ->with(Area::AREA_ADMINHTML);
+ $this->scopeMock->expects($this->at(2))
->method('setCurrentScope')
->with('oldScope');
- $this->flagMock->expects($this->once())
- ->method('setFlagData')
- ->with($data);
- $this->flagResourceMock->expects($this->once())
- ->method('save')
- ->with($this->flagMock);
+ $this->flagManagerMock->expects($this->once())
+ ->method('saveFlag')
+ ->with(Importer::FLAG_CODE, $data);
$this->assertSame(['System config was processed'], $this->model->import($data));
}
@@ -176,10 +177,7 @@ public function testImportWithException()
$data = [];
$currentData = ['current' => '2'];
- $this->flagResourceMock->expects($this->once())
- ->method('load')
- ->with($this->flagMock, Importer::FLAG_CODE, 'flag_code');
- $this->flagMock->expects($this->once())
+ $this->flagManagerMock->expects($this->once())
->method('getFlagData')
->willReturn($currentData);
$this->arrayUtilsMock->expects($this->exactly(2))
diff --git a/app/code/Magento/Config/etc/di.xml b/app/code/Magento/Config/etc/di.xml
index 1abc5e35f578f..23d5d39a6e5bc 100644
--- a/app/code/Magento/Config/etc/di.xml
+++ b/app/code/Magento/Config/etc/di.xml
@@ -84,6 +84,14 @@
Magento\Framework\App\Cache\Type\Config
systemConfigPreProcessorComposite
Magento\Framework\Serialize\Serializer\Serialize
+ Magento\Config\App\Config\Type\System\Reader\Proxy
+
+
+
+
+ systemConfigSourceAggregated
+ systemConfigPostProcessorComposite
+ systemConfigPreProcessorComposite
@@ -288,7 +296,8 @@
-
-
- Magento\Config\Model\Config\Importer
+ - Magento\Config\Model\Config\Importer
+ - 30
diff --git a/app/code/Magento/Contact/Model/Mail.php b/app/code/Magento/Contact/Model/Mail.php
index 9a1f1ff8a2063..d63efbbca573b 100644
--- a/app/code/Magento/Contact/Model/Mail.php
+++ b/app/code/Magento/Contact/Model/Mail.php
@@ -6,7 +6,9 @@
namespace Magento\Contact\Model;
use Magento\Framework\Mail\Template\TransportBuilder;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Translate\Inline\StateInterface;
+use Magento\Store\Model\StoreManagerInterface;
class Mail implements MailInterface
{
@@ -26,18 +28,29 @@ class Mail implements MailInterface
private $inlineTranslation;
/**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
+ /**
+ * Initialize dependencies.
+ *
* @param ConfigInterface $contactsConfig
* @param TransportBuilder $transportBuilder
* @param StateInterface $inlineTranslation
+ * @param StoreManagerInterface|null $storeManager
*/
public function __construct(
ConfigInterface $contactsConfig,
TransportBuilder $transportBuilder,
- StateInterface $inlineTranslation
+ StateInterface $inlineTranslation,
+ StoreManagerInterface $storeManager = null
) {
$this->contactsConfig = $contactsConfig;
$this->transportBuilder = $transportBuilder;
$this->inlineTranslation = $inlineTranslation;
+ $this->storeManager = $storeManager ?:
+ ObjectManager::getInstance()->get(StoreManagerInterface::class);
}
/**
@@ -58,8 +71,8 @@ public function send($replyTo, array $variables)
->setTemplateIdentifier($this->contactsConfig->emailTemplate())
->setTemplateOptions(
[
- 'area' => 'adminhtml',
- 'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID,
+ 'area' => 'frontend',
+ 'store' => $this->storeManager->getStore()->getId()
]
)
->setTemplateVars($variables)
diff --git a/app/code/Magento/Contact/Test/Unit/Model/MailTest.php b/app/code/Magento/Contact/Test/Unit/Model/MailTest.php
index e1e8fec435125..f432a4fc5ccee 100644
--- a/app/code/Magento/Contact/Test/Unit/Model/MailTest.php
+++ b/app/code/Magento/Contact/Test/Unit/Model/MailTest.php
@@ -8,6 +8,8 @@
use Magento\Contact\Model\ConfigInterface;
use Magento\Contact\Model\Mail;
+use Magento\Store\Model\StoreManagerInterface;
+use Magento\Store\Api\Data\StoreInterface;
class MailTest extends \PHPUnit_Framework_TestCase
{
@@ -32,6 +34,11 @@ class MailTest extends \PHPUnit_Framework_TestCase
*/
private $inlineTranslationMock;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $storeManagerMock;
+
/**
* @var Mail
*/
@@ -50,10 +57,13 @@ protected function setUp()
)->disableOriginalConstructor(
)->getMock();
+ $this->storeManagerMock = $this->getMock(StoreManagerInterface::class);
+
$this->mail = new Mail(
$this->configMock,
$this->transportBuilderMock,
- $this->inlineTranslationMock
+ $this->inlineTranslationMock,
+ $this->storeManagerMock
);
}
@@ -64,6 +74,11 @@ public function testSendMail()
$transport = $this->getMock(\Magento\Framework\Mail\TransportInterface::class, [], [], '', false);
+ $store = $this->getMock(StoreInterface::class);
+ $store->expects($this->once())->method('getId')->willReturn(555);
+
+ $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($store);
+
$this->transportBuilderMock->expects($this->once())
->method('setTemplateIdentifier')
->will($this->returnSelf());
@@ -71,8 +86,8 @@ public function testSendMail()
$this->transportBuilderMock->expects($this->once())
->method('setTemplateOptions')
->with([
- 'area' => \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE,
- 'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID,
+ 'area' => 'frontend',
+ 'store' => 555,
])
->will($this->returnSelf());
diff --git a/app/code/Magento/Contact/etc/email_templates.xml b/app/code/Magento/Contact/etc/email_templates.xml
index 8ae3b643f43c8..8f3f5ee442f7d 100644
--- a/app/code/Magento/Contact/etc/email_templates.xml
+++ b/app/code/Magento/Contact/etc/email_templates.xml
@@ -6,5 +6,5 @@
*/
-->
-
+
diff --git a/app/code/Magento/Contact/view/adminhtml/email/submitted_form.html b/app/code/Magento/Contact/view/adminhtml/email/submitted_form.html
deleted file mode 100644
index f65d67e6cafc3..0000000000000
--- a/app/code/Magento/Contact/view/adminhtml/email/submitted_form.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-{{trans "Name: %name" name=$data.name}}
-{{trans "Email: %email" email=$data.email}}
-{{trans "Phone Number: %telephone" telephone=$data.telephone}}
-
-{{trans "Comment: %comment" comment=$data.comment}}
diff --git a/app/code/Magento/Contact/view/frontend/email/submitted_form.html b/app/code/Magento/Contact/view/frontend/email/submitted_form.html
new file mode 100644
index 0000000000000..1bce6159c586a
--- /dev/null
+++ b/app/code/Magento/Contact/view/frontend/email/submitted_form.html
@@ -0,0 +1,34 @@
+
+
+
+
+{{template config_path="design/email/header_template"}}
+
+
+
+ {{trans "Name"}} |
+ {{var data.name}} |
+
+
+ {{trans "Email"}} |
+ {{var data.email}} |
+
+
+ {{trans "Phone"}} |
+ {{var data.telephone}} |
+
+
+{{trans "Message"}}
+{{var data.comment}}
+
+{{template config_path="design/email/footer_template"}}
diff --git a/app/code/Magento/Deploy/Console/Command/App/ConfigImport/Processor.php b/app/code/Magento/Deploy/Console/Command/App/ConfigImport/Processor.php
index 8dec0969df4fc..34a5001916213 100644
--- a/app/code/Magento/Deploy/Console/Command/App/ConfigImport/Processor.php
+++ b/app/code/Magento/Deploy/Console/Command/App/ConfigImport/Processor.php
@@ -11,6 +11,7 @@
use Magento\Deploy\Model\DeploymentConfig\ChangeDetector;
use Magento\Deploy\Model\DeploymentConfig\ImporterPool;
use Magento\Deploy\Model\DeploymentConfig\Hash;
+use Magento\Framework\Exception\ValidatorException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Magento\Deploy\Model\DeploymentConfig\ImporterFactory;
@@ -113,10 +114,10 @@ public function execute(InputInterface $input, OutputInterface $output)
if (!$importers || !$this->changeDetector->hasChanges()) {
$output->writeln('Nothing to import.');
return;
- } else {
- $output->writeln('Processing configurations data from configuration file...');
}
+ $output->writeln('Processing configurations data from configuration file...');
+
/**
* @var string $section
* @var string $importerClassName
@@ -126,9 +127,11 @@ public function execute(InputInterface $input, OutputInterface $output)
continue;
}
+ $data = (array)$this->deploymentConfig->getConfigData($section);
+ $this->validateSectionData($section, $data);
+
/** @var ImporterInterface $importer */
$importer = $this->importerFactory->create($importerClassName);
- $data = (array)$this->deploymentConfig->getConfigData($section);
$warnings = $importer->getWarningMessages($data);
$questions = array_merge($warnings, ['Do you want to continue [yes/no]?']);
@@ -149,4 +152,23 @@ public function execute(InputInterface $input, OutputInterface $output)
throw new RuntimeException(__('Import failed: %1', $exception->getMessage()), $exception);
}
}
+
+ /**
+ * Validates that current section has valid import data
+ *
+ * @param string $section Name of configuration section
+ * @param array $data Configuration data for given section
+ * @return void
+ * @throws ValidatorException If current section has wrong data
+ */
+ private function validateSectionData($section, array $data)
+ {
+ $validator = $this->configImporterPool->getValidator($section);
+ if (null !== $validator) {
+ $messages = $validator->validate($data);
+ if (!empty($messages)) {
+ throw new ValidatorException(__(implode(PHP_EOL, $messages)));
+ }
+ }
+ }
}
diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/SensitiveConfigSetFacade.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/SensitiveConfigSetFacade.php
index 319b358c4ccc6..7ca4fee416d68 100644
--- a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/SensitiveConfigSetFacade.php
+++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/SensitiveConfigSetFacade.php
@@ -78,12 +78,13 @@ public function __construct(
}
/**
- * Processes the sensitive:config:set command.
+ * Processes the config:sensitive:set command.
*
* @param InputInterface $input The input manager
* @param OutputInterface $output The output manager
* @return void
- * @throws RuntimeException If data can not be processed
+ * @throws LocalizedException If scope or scope code is not valid
+ * @throws RuntimeException If sensitive config can not be filled
*/
public function process(InputInterface $input, OutputInterface $output)
{
diff --git a/app/code/Magento/Deploy/Model/DeploymentConfig/ChangeDetector.php b/app/code/Magento/Deploy/Model/DeploymentConfig/ChangeDetector.php
index 63b0d63f7312f..83ec68b88cd61 100644
--- a/app/code/Magento/Deploy/Model/DeploymentConfig/ChangeDetector.php
+++ b/app/code/Magento/Deploy/Model/DeploymentConfig/ChangeDetector.php
@@ -68,6 +68,9 @@ public function hasChanges($sectionName = null)
$hashes = $this->configHash->get();
foreach ($configs as $section => $config) {
+ if (null === $config) {
+ continue;
+ }
$savedHash = isset($hashes[$section]) ? $hashes[$section] : null;
$generatedHash = empty($config) && !$savedHash ? null : $this->hashGenerator->generate($config);
if ($generatedHash !== $savedHash) {
diff --git a/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterFactory.php b/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterFactory.php
index e714f323ac061..bba90d2b0327c 100644
--- a/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterFactory.php
+++ b/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterFactory.php
@@ -44,7 +44,7 @@ public function create($className, array $data = [])
if (!$importer instanceof ImporterInterface) {
throw new \InvalidArgumentException(
- 'Type "' . $className . '" is not instance on ' . ImporterInterface::class
+ 'Type "' . $className . '" is not instance of ' . ImporterInterface::class
);
}
diff --git a/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php b/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php
index 22a92504f592a..9a987c704dea0 100644
--- a/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php
+++ b/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Deploy\Model\DeploymentConfig;
+use Magento\Framework\App\DeploymentConfig\ValidatorInterface;
use Magento\Framework\Exception\ConfigurationMismatchException;
use Magento\Framework\ObjectManagerInterface;
@@ -18,19 +19,20 @@ class ImporterPool
/**
* List of sections and their importers.
*
- * Sections are defined with importers in di.xml
+ * Sections are defined with importers in di.xml. Every section may have data validator
* E.g.
* ```xml
*
*
*
* -
- *
- 20
- * - Magento\Store\Model\StoreImporter
+ * - 20
+ * - Magento\Store\Model\StoreImporter
+ * - Magento\Store\Model\Config\StoreValidator
*
* -
- *
- 10
- * - Magento\Theme\Model\ThemeImporter
+ * - 10
+ * - Magento\Theme\Model\ThemeImporter
*
*
*
@@ -62,7 +64,7 @@ class ImporterPool
/**
* Sorted list of importers class names.
*
- * This list sorted by parameter "sortOrder", that defined in di.xml
+ * This list sorted by parameter "sort_order", that defined in di.xml
*
* ```php
* [
@@ -83,13 +85,26 @@ class ImporterPool
*/
private $objectManager;
+ /**
+ * Factory that creates validator objects by class name.
+ * Validators should be instances of Magento\Framework\App\DeploymentConfig\ValidatorInterface
+ *
+ * @var ValidatorFactory
+ */
+ private $validatorFactory;
+
/**
* @param ObjectManagerInterface $objectManager the Magento object manager
+ * @param ValidatorFactory $validatorFactory the validator factory
* @param array $importers the list of sections and their importers
*/
- public function __construct(ObjectManagerInterface $objectManager, array $importers = [])
- {
+ public function __construct(
+ ObjectManagerInterface $objectManager,
+ ValidatorFactory $validatorFactory,
+ array $importers = []
+ ) {
$this->objectManager = $objectManager;
+ $this->validatorFactory = $validatorFactory;
$this->importers = $importers;
}
@@ -113,7 +128,7 @@ public function getSections()
}
/**
- * Retrieves list of all sections with their importer class names, sorted by sortOrder.
+ * Retrieves list of all sections with their importer class names, sorted by sort_order.
*
* E.g.
* ```php
@@ -132,11 +147,11 @@ public function getImporters()
$sortedImporters = [];
foreach ($this->sort($this->importers) as $section => $importer) {
- if (empty($importer['class'])) {
- throw new ConfigurationMismatchException(__('Parameter "class" must be present.'));
+ if (empty($importer['importer_class'])) {
+ throw new ConfigurationMismatchException(__('Parameter "importer_class" must be present.'));
}
- $sortedImporters[$section] = $importer['class'];
+ $sortedImporters[$section] = $importer['importer_class'];
}
$this->sortedImporters = $sortedImporters;
@@ -145,6 +160,21 @@ public function getImporters()
return $this->sortedImporters;
}
+ /**
+ * Returns validator object for section if it was declared, otherwise returns null.
+ *
+ * @param string $section Section name
+ * @return ValidatorInterface|null
+ * @throws \InvalidArgumentException
+ */
+ public function getValidator($section)
+ {
+ if (isset($this->importers[$section]) && !empty($this->importers[$section]['validator_class'])) {
+ return $this->validatorFactory->create($this->importers[$section]['validator_class']);
+ }
+ return null;
+ }
+
/**
* Sorts importers according to sort order.
*
@@ -154,14 +184,14 @@ public function getImporters()
private function sort(array $data)
{
uasort($data, function (array $a, array $b) {
- $a['sortOrder'] = $this->getSortOrder($a);
- $b['sortOrder'] = $this->getSortOrder($b);
+ $a['sort_order'] = $this->getSortOrder($a);
+ $b['sort_order'] = $this->getSortOrder($b);
- if ($a['sortOrder'] == $b['sortOrder']) {
+ if ($a['sort_order'] == $b['sort_order']) {
return 0;
}
- return ($a['sortOrder'] < $b['sortOrder']) ? -1 : 1;
+ return ($a['sort_order'] < $b['sort_order']) ? -1 : 1;
});
return $data;
@@ -175,6 +205,6 @@ private function sort(array $data)
*/
private function getSortOrder(array $variable)
{
- return !empty($variable['sortOrder']) ? $variable['sortOrder'] : 0;
+ return !empty($variable['sort_order']) ? $variable['sort_order'] : 0;
}
}
diff --git a/app/code/Magento/Deploy/Model/DeploymentConfig/ValidatorFactory.php b/app/code/Magento/Deploy/Model/DeploymentConfig/ValidatorFactory.php
new file mode 100644
index 0000000000000..d07f7041bb58f
--- /dev/null
+++ b/app/code/Magento/Deploy/Model/DeploymentConfig/ValidatorFactory.php
@@ -0,0 +1,53 @@
+objectManager = $objectManager;
+ }
+
+ /**
+ * Creates object instance by class name.
+ *
+ * @param string $className the name of class for creation of its object instance
+ * @param array $data the array with some additional configuration data for creation of object instance
+ * @return ValidatorInterface the created object instance
+ * @throws \InvalidArgumentException is thrown when object instance does not implement ValidatorInterface
+ */
+ public function create($className, array $data = [])
+ {
+ $importer = $this->objectManager->create($className, $data);
+
+ if (!$importer instanceof ValidatorInterface) {
+ throw new \InvalidArgumentException(
+ 'Type "' . $className . '" is not instance of ' . ValidatorInterface::class
+ );
+ }
+
+ return $importer;
+ }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/ConfigImport/ProcessorTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/ConfigImport/ProcessorTest.php
index 4041ac5fd7432..3e7118cfc8ec5 100644
--- a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/ConfigImport/ProcessorTest.php
+++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/ConfigImport/ProcessorTest.php
@@ -16,6 +16,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Magento\Deploy\Model\DeploymentConfig\ImporterFactory;
use Magento\Framework\Console\QuestionPerformer\YesNo;
+use Magento\Framework\App\DeploymentConfig\ValidatorInterface;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -234,6 +235,48 @@ public function testImportWithException()
$this->processor->execute($this->inputMock, $this->outputMock);
}
+ /**
+ * @expectedException \Magento\Framework\Exception\RuntimeException
+ * @expectedExceptionMessage Import failed: error message
+ */
+ public function testImportWithValidation()
+ {
+ $configData = ['config data'];
+ $importerClassName = 'someImporterClassName';
+ $importers = ['someSection' => $importerClassName];
+ $errorMessages = ['error message'];
+
+ $validatorMock = $this->getMockBuilder(ValidatorInterface::class)
+ ->getMockForAbstractClass();
+ $validatorMock->expects($this->once())
+ ->method('validate')
+ ->with($configData)
+ ->willReturn($errorMessages);
+ $this->configImporterPoolMock->expects($this->once())
+ ->method('getImporters')
+ ->willReturn($importers);
+ $this->changeDetectorMock->expects($this->exactly(2))
+ ->method('hasChanges')
+ ->withConsecutive(
+ [],
+ ['someSection']
+ )
+ ->willReturnOnConsecutiveCalls(true, true);
+ $this->deploymentConfigMock->expects($this->once())
+ ->method('getConfigData')
+ ->with('someSection')
+ ->willReturn($configData);
+ $this->configImporterPoolMock->expects($this->once())
+ ->method('getValidator')
+ ->willReturn($validatorMock);
+ $this->importerFactoryMock->expects($this->never())
+ ->method('create');
+ $this->loggerMock->expects($this->once())
+ ->method('error');
+
+ $this->processor->execute($this->inputMock, $this->outputMock);
+ }
+
/**
* @param array $importers
* @param bool $isValid
diff --git a/app/code/Magento/Deploy/Test/Unit/Model/DeploymentConfig/ImporterFactoryTest.php b/app/code/Magento/Deploy/Test/Unit/Model/DeploymentConfig/ImporterFactoryTest.php
index ce6240c31f1ba..228538dcf6034 100644
--- a/app/code/Magento/Deploy/Test/Unit/Model/DeploymentConfig/ImporterFactoryTest.php
+++ b/app/code/Magento/Deploy/Test/Unit/Model/DeploymentConfig/ImporterFactoryTest.php
@@ -47,7 +47,7 @@ public function testCreate()
/**
* @expectedException \InvalidArgumentException
* @codingStandardsIgnoreStart
- * @expectedExceptionMessage Type "some/class/name" is not instance on Magento\Framework\App\DeploymentConfig\ImporterInterface
+ * @expectedExceptionMessage Type "some/class/name" is not instance of Magento\Framework\App\DeploymentConfig\ImporterInterface
* @codingStandardsIgnoreEnd
*/
public function testCreateWithInvalidArgumentException()
diff --git a/app/code/Magento/Deploy/Test/Unit/Model/DeploymentConfig/ImporterPoolTest.php b/app/code/Magento/Deploy/Test/Unit/Model/DeploymentConfig/ImporterPoolTest.php
index 57f0fd15efa8c..675d46185f73b 100644
--- a/app/code/Magento/Deploy/Test/Unit/Model/DeploymentConfig/ImporterPoolTest.php
+++ b/app/code/Magento/Deploy/Test/Unit/Model/DeploymentConfig/ImporterPoolTest.php
@@ -6,7 +6,10 @@
namespace Magento\Deploy\Test\Unit\Model\DeploymentConfig;
use Magento\Deploy\Model\DeploymentConfig\ImporterPool;
+use Magento\Deploy\Model\DeploymentConfig\ValidatorFactory;
+use Magento\Framework\App\DeploymentConfig\ValidatorInterface;
use Magento\Framework\ObjectManagerInterface;
+use PHPUnit_Framework_MockObject_MockObject as Mock;
class ImporterPoolTest extends \PHPUnit_Framework_TestCase
{
@@ -16,10 +19,15 @@ class ImporterPoolTest extends \PHPUnit_Framework_TestCase
private $configImporterPool;
/**
- * @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var ObjectManagerInterface|Mock
*/
private $objectManagerMock;
+ /**
+ * @var ValidatorFactory|Mock
+ */
+ private $validatorFactoryMock;
+
/**
* @return void
*/
@@ -27,12 +35,19 @@ protected function setUp()
{
$this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class)
->getMockForAbstractClass();
+ $this->validatorFactoryMock = $this->getMockBuilder(ValidatorFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->configImporterPool = new ImporterPool(
$this->objectManagerMock,
+ $this->validatorFactoryMock,
[
- 'firstSection' => ['class' => 'Magento\Importer\SomeImporter', 'sortOrder' => 20],
- 'secondSection' => ['class' => 'Magento\Importer\SomeImporter'],
- 'thirdSection' => ['class' => 'Magento\Importer\SomeImporter', 'sortOrder' => 10]
+ 'firstSection' => ['importer_class' => 'Magento\Importer\SomeImporter', 'sort_order' => 20],
+ 'secondSection' => [
+ 'importer_class' => 'Magento\Importer\SomeImporter',
+ 'validator_class' => 'Validator\SomeValidator\Class'
+ ],
+ 'thirdSection' => ['importer_class' => 'Magento\Importer\SomeImporter', 'sort_order' => 10]
]
);
}
@@ -53,12 +68,13 @@ public function testGetImporters()
/**
* @return void
* @expectedException \Magento\Framework\Exception\ConfigurationMismatchException
- * @expectedExceptionMessage Parameter "class" must be present.
+ * @expectedExceptionMessage Parameter "importer_class" must be present.
*/
public function testGetImportersEmptyParameterClass()
{
$this->configImporterPool = new ImporterPool(
$this->objectManagerMock,
+ $this->validatorFactoryMock,
['wrongSection' => ['class' => '']]
);
@@ -75,4 +91,21 @@ public function testGetSections()
$this->configImporterPool->getSections()
);
}
+
+ public function testGetValidator()
+ {
+ $validatorMock = $this->getMockBuilder(ValidatorInterface::class)
+ ->getMockForAbstractClass();
+ $this->validatorFactoryMock->expects($this->once())
+ ->method('create')
+ ->with('Validator\SomeValidator\Class')
+ ->willReturn($validatorMock);
+
+ $this->assertNull($this->configImporterPool->getValidator('firstSection'));
+ $this->assertNull($this->configImporterPool->getValidator('thirdSection'));
+ $this->assertInstanceOf(
+ ValidatorInterface::class,
+ $this->configImporterPool->getValidator('secondSection')
+ );
+ }
}
diff --git a/app/code/Magento/Deploy/Test/Unit/Model/DeploymentConfig/ValidatorFactoryTest.php b/app/code/Magento/Deploy/Test/Unit/Model/DeploymentConfig/ValidatorFactoryTest.php
new file mode 100644
index 0000000000000..dca357df6d1e2
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Model/DeploymentConfig/ValidatorFactoryTest.php
@@ -0,0 +1,69 @@
+objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class)
+ ->getMockForAbstractClass();
+
+ $this->model = new ValidatorFactory($this->objectManagerMock);
+ }
+
+ public function testCreate()
+ {
+ $validatorMock = $this->getMockBuilder(Validator::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->objectManagerMock->expects($this->once())
+ ->method('create')
+ ->with(Validator::class)
+ ->willReturn($validatorMock);
+
+ $this->assertInstanceOf(
+ Validator::class,
+ $this->model->create(Validator::class)
+ );
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @codingStandardsIgnoreStart
+ * @expectedExceptionMessage Type "className" is not instance of Magento\Framework\App\DeploymentConfig\ValidatorInterface
+ * @codingStandardsIgnoreEnd
+ */
+ public function testCreateWrongImplementation()
+ {
+ $className = 'className';
+
+ $stdMock = $this->getMockBuilder(\StdClass::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->objectManagerMock->expects($this->once())
+ ->method('create')
+ ->with($className, [])
+ ->willReturn($stdMock);
+
+ $this->model->create($className);
+ }
+}
diff --git a/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php b/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php
index d718c661fb2e8..eb0a1c2efbd5c 100644
--- a/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php
+++ b/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php
@@ -7,11 +7,11 @@
use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
use Magento\Store\Model\Store;
+use Magento\Framework\App\ObjectManager;
/**
* Sitemap resource product collection model
*
- * @author Magento Core Team
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Product extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
@@ -71,9 +71,20 @@ class Product extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
/**
* @var \Magento\Catalog\Model\Product\Media\Config
+ * @deprecated unused
*/
protected $_mediaConfig;
+ /**
+ * @var \Magento\Catalog\Model\Product
+ */
+ private $productModel;
+
+ /**
+ * @var \Magento\Catalog\Helper\Image
+ */
+ private $catalogImageHelper;
+
/**
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
* @param \Magento\Sitemap\Helper\Data $sitemapData
@@ -85,6 +96,8 @@ class Product extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
* @param \Magento\Catalog\Model\Product\Gallery\ReadHandler $mediaGalleryReadHandler
* @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig
* @param string $connectionName
+ * @param \Magento\Catalog\Model\Product $productModel
+ * @param \Magento\Catalog\Helper\Image $catalogImageHelper
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -97,7 +110,9 @@ public function __construct(
\Magento\Catalog\Model\ResourceModel\Product\Gallery $mediaGalleryResourceModel,
\Magento\Catalog\Model\Product\Gallery\ReadHandler $mediaGalleryReadHandler,
\Magento\Catalog\Model\Product\Media\Config $mediaConfig,
- $connectionName = null
+ $connectionName = null,
+ \Magento\Catalog\Model\Product $productModel = null,
+ \Magento\Catalog\Helper\Image $catalogImageHelper = null
) {
$this->_productResource = $productResource;
$this->_storeManager = $storeManager;
@@ -107,6 +122,9 @@ public function __construct(
$this->mediaGalleryReadHandler = $mediaGalleryReadHandler;
$this->_mediaConfig = $mediaConfig;
$this->_sitemapData = $sitemapData;
+ $this->productModel = $productModel ?: ObjectManager::getInstance()->get(\Magento\Catalog\Model\Product::class);
+ $this->catalogImageHelper = $catalogImageHelper ?: ObjectManager::getInstance()
+ ->get(\Magento\Catalog\Helper\Image::class);
parent::__construct($context, $connectionName);
}
@@ -339,7 +357,7 @@ protected function _loadProductImages($product, $storeId)
) {
$imagesCollection = [
new \Magento\Framework\DataObject(
- ['url' => $this->_getMediaConfig()->getBaseMediaUrlAddition() . $product->getImage()]
+ ['url' => $this->getProductImageUrl($product->getImage())]
),
];
}
@@ -348,7 +366,7 @@ protected function _loadProductImages($product, $storeId)
// Determine thumbnail path
$thumbnail = $product->getThumbnail();
if ($thumbnail && $product->getThumbnail() != self::NOT_SELECTED_IMAGE) {
- $thumbnail = $this->_getMediaConfig()->getBaseMediaUrlAddition() . $thumbnail;
+ $thumbnail = $this->getProductImageUrl($thumbnail);
} else {
$thumbnail = $imagesCollection[0]->getUrl();
}
@@ -378,11 +396,10 @@ protected function _getAllProductImages($product, $storeId)
$imagesCollection = [];
if ($gallery) {
- $productMediaPath = $this->_getMediaConfig()->getBaseMediaUrlAddition();
foreach ($gallery as $image) {
$imagesCollection[] = new \Magento\Framework\DataObject(
[
- 'url' => $productMediaPath . $image['file'],
+ 'url' => $this->getProductImageUrl($image['file']),
'caption' => $image['label'] ? $image['label'] : $image['label_default'],
]
);
@@ -396,9 +413,28 @@ protected function _getAllProductImages($product, $storeId)
* Get media config
*
* @return \Magento\Catalog\Model\Product\Media\Config
+ * @deprecated No longer used, as we're getting full image URL from getProductImageUrl method
+ * @see getProductImageUrl()
*/
protected function _getMediaConfig()
{
return $this->_mediaConfig;
}
+
+ /**
+ * Get product image URL from image filename and path
+ *
+ * @param string $image
+ * @return string
+ */
+ private function getProductImageUrl($image)
+ {
+ $productObject = $this->productModel;
+ $imgUrl = $this->catalogImageHelper
+ ->init($productObject, 'product_page_image_large')
+ ->setImageFile($image)
+ ->getUrl();
+
+ return $imgUrl;
+ }
}
diff --git a/app/code/Magento/Sitemap/Model/Sitemap.php b/app/code/Magento/Sitemap/Model/Sitemap.php
index 13a90cc627769..6abc13c98c35e 100644
--- a/app/code/Magento/Sitemap/Model/Sitemap.php
+++ b/app/code/Magento/Sitemap/Model/Sitemap.php
@@ -448,7 +448,7 @@ protected function _isSplitRequired($row)
* @param null|string $lastmod
* @param null|string $changefreq
* @param null|string $priority
- * @param null|array $images
+ * @param null|array|\Magento\Framework\DataObject $images
* @return string
* Sitemap images
* @see http://support.google.com/webmasters/bin/answer.py?hl=en&answer=178636
@@ -473,7 +473,7 @@ protected function _getSitemapRow($url, $lastmod = null, $changefreq = null, $pr
// Add Images to sitemap
foreach ($images->getCollection() as $image) {
$row .= '';
- $row .= '' . htmlspecialchars($this->_getMediaUrl($image->getUrl())) . '';
+ $row .= '' . htmlspecialchars($image->getUrl()) . '';
$row .= '' . htmlspecialchars($images->getTitle()) . '';
if ($image->getCaption()) {
$row .= '' . htmlspecialchars($image->getCaption()) . '';
@@ -483,9 +483,7 @@ protected function _getSitemapRow($url, $lastmod = null, $changefreq = null, $pr
// Add PageMap image for Google web search
$row .= '';
$row .= '';
- $row .= '';
+ $row .= '';
$row .= '';
}
@@ -591,6 +589,7 @@ protected function _getBaseDir()
*/
protected function _getStoreBaseUrl($type = \Magento\Framework\UrlInterface::URL_TYPE_LINK)
{
+ /** @var \Magento\Store\Model\Store $store */
$store = $this->_storeManager->getStore($this->getStoreId());
$isSecure = $store->isUrlSecure();
return rtrim($store->getBaseUrl($type, $isSecure), '/') . '/';
@@ -613,6 +612,8 @@ protected function _getUrl($url, $type = \Magento\Framework\UrlInterface::URL_TY
*
* @param string $url
* @return string
+ * @deprecated No longer used, as we're generating product image URLs inside collection instead
+ * @see \Magento\Sitemap\Model\ResourceModel\Catalog\Product::_loadProductImages()
*/
protected function _getMediaUrl($url)
{
diff --git a/app/code/Magento/Sitemap/Test/Unit/Model/SitemapTest.php b/app/code/Magento/Sitemap/Test/Unit/Model/SitemapTest.php
index 241e394187147..fb1379ecfca28 100644
--- a/app/code/Magento/Sitemap/Test/Unit/Model/SitemapTest.php
+++ b/app/code/Magento/Sitemap/Test/Unit/Model/SitemapTest.php
@@ -535,6 +535,8 @@ protected function _getModelMock($mockBeforeSave = false)
]
)
);
+
+ $storeBaseMediaUrl = 'http://store.com/pub/media/catalog/product/cache/c9e0b0ef589f3508e5ba515cde53c5ff/';
$this->_sitemapProductMock->expects(
$this->any()
)->method(
@@ -553,13 +555,16 @@ protected function _getModelMock($mockBeforeSave = false)
[
'collection' => [
new \Magento\Framework\DataObject(
- ['url' => 'image1.png', 'caption' => 'caption & > title < "']
+ [
+ 'url' => $storeBaseMediaUrl.'i/m/image1.png',
+ 'caption' => 'caption & > title < "'
+ ]
),
new \Magento\Framework\DataObject(
- ['url' => 'image_no_caption.png', 'caption' => null]
+ ['url' => $storeBaseMediaUrl.'i/m/image_no_caption.png', 'caption' => null]
),
],
- 'thumbnail' => 'thumbnail.jpg',
+ 'thumbnail' => $storeBaseMediaUrl.'t/h/thumbnail.jpg',
'title' => 'Product & > title < "',
]
),
diff --git a/app/code/Magento/Sitemap/Test/Unit/Model/_files/sitemap-1-4.xml b/app/code/Magento/Sitemap/Test/Unit/Model/_files/sitemap-1-4.xml
index e9fd2b00a909c..7111154efbf85 100644
--- a/app/code/Magento/Sitemap/Test/Unit/Model/_files/sitemap-1-4.xml
+++ b/app/code/Magento/Sitemap/Test/Unit/Model/_files/sitemap-1-4.xml
@@ -14,18 +14,18 @@
monthly
0.5
- http://store.com/image1.png
+ http://store.com/pub/media/catalog/product/cache/c9e0b0ef589f3508e5ba515cde53c5ff/i/m/image1.png
Product & > title < "
caption & > title < "
- http://store.com/image_no_caption.png
+ http://store.com/pub/media/catalog/product/cache/c9e0b0ef589f3508e5ba515cde53c5ff/i/m/image_no_caption.png
Product & > title < "
-
+
diff --git a/app/code/Magento/Sitemap/Test/Unit/Model/_files/sitemap-single.xml b/app/code/Magento/Sitemap/Test/Unit/Model/_files/sitemap-single.xml
index d4801a014aabc..e79e022c98995 100644
--- a/app/code/Magento/Sitemap/Test/Unit/Model/_files/sitemap-single.xml
+++ b/app/code/Magento/Sitemap/Test/Unit/Model/_files/sitemap-single.xml
@@ -32,18 +32,18 @@
monthly
0.5
- http://store.com/image1.png
+ http://store.com/pub/media/catalog/product/cache/c9e0b0ef589f3508e5ba515cde53c5ff/i/m/image1.png
Product & > title < "
caption & > title < "
- http://store.com/image_no_caption.png
+ http://store.com/pub/media/catalog/product/cache/c9e0b0ef589f3508e5ba515cde53c5ff/i/m/image_no_caption.png
Product & > title < "
-
+
diff --git a/app/code/Magento/Store/App/Config/Source/InitialConfigSource.php b/app/code/Magento/Store/App/Config/Source/InitialConfigSource.php
new file mode 100644
index 0000000000000..984e8330c69b6
--- /dev/null
+++ b/app/code/Magento/Store/App/Config/Source/InitialConfigSource.php
@@ -0,0 +1,89 @@
+reader = $reader;
+ $this->deploymentConfig = $deploymentConfig;
+ $this->dataObjectFactory = $dataObjectFactory;
+ $this->configType = $configType;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function get($path = '')
+ {
+ /**
+ * Magento store configuration should not be read from file source
+ * on not installed instance.
+ *
+ * @see \Magento\Store\Model\Config\Importer To import store configs
+ */
+ if (!$this->deploymentConfig->isAvailable()) {
+ return [];
+ }
+
+ $data = $this->dataObjectFactory->create([
+ 'data' => $this->reader->load()
+ ]);
+
+ if ($path !== '' && $path !== null) {
+ $path = '/' . ltrim($path, '/');
+ }
+
+ return $data->getData($this->configType . $path) ?: [];
+ }
+}
diff --git a/app/code/Magento/Store/Model/Config/Importer.php b/app/code/Magento/Store/Model/Config/Importer.php
index 130e523ab53df..1915175f0ec43 100644
--- a/app/code/Magento/Store/Model/Config/Importer.php
+++ b/app/code/Magento/Store/Model/Config/Importer.php
@@ -84,8 +84,8 @@ public function __construct(
public function import(array $data)
{
$actions = [
- ProcessorFactory::TYPE_DELETE,
ProcessorFactory::TYPE_CREATE,
+ ProcessorFactory::TYPE_DELETE,
ProcessorFactory::TYPE_UPDATE
];
$messages = ['Stores were processed'];
diff --git a/app/code/Magento/Store/Model/Config/Importer/Processor/Create.php b/app/code/Magento/Store/Model/Config/Importer/Processor/Create.php
index 51b6e45aff10a..768482aa44636 100644
--- a/app/code/Magento/Store/Model/Config/Importer/Processor/Create.php
+++ b/app/code/Magento/Store/Model/Config/Importer/Processor/Create.php
@@ -92,6 +92,10 @@ public function run(array $data)
];
foreach ($entities as $scope) {
+ if (!isset($data[$scope])) {
+ continue;
+ }
+
$items = $this->dataDifferenceCalculator->getItemsToCreate($scope, $data[$scope]);
if (!$items) {
@@ -125,15 +129,20 @@ public function run(array $data)
private function createWebsites(array $items, array $data)
{
foreach ($items as $websiteData) {
- unset($websiteData['website_id']);
+ $groupId = $websiteData['default_group_id'];
+
+ unset(
+ $websiteData['website_id'],
+ $websiteData['default_group_id']
+ );
$website = $this->websiteFactory->create();
$website->setData($websiteData);
$website->getResource()->save($website);
- $website->getResource()->addCommitCallback(function () use ($website, $data) {
+ $website->getResource()->addCommitCallback(function () use ($website, $data, $groupId) {
$website->setDefaultGroupId(
- $this->detectGroupById($data, $website->getDefaultGroupId())->getId()
+ $this->detectGroupById($data, $groupId)->getId()
);
$website->getResource()->save($website);
});
diff --git a/app/code/Magento/Store/Model/Config/Importer/Processor/Delete.php b/app/code/Magento/Store/Model/Config/Importer/Processor/Delete.php
index 5cb3a2a381428..8660a0ba7152d 100644
--- a/app/code/Magento/Store/Model/Config/Importer/Processor/Delete.php
+++ b/app/code/Magento/Store/Model/Config/Importer/Processor/Delete.php
@@ -111,6 +111,10 @@ public function run(array $data)
];
foreach ($entities as $scope) {
+ if (!isset($data[$scope])) {
+ continue;
+ }
+
$items = $this->dataDifferenceCalculator->getItemsToDelete($scope, $data[$scope]);
if (!$items) {
diff --git a/app/code/Magento/Store/Model/Config/Importer/Processor/Update.php b/app/code/Magento/Store/Model/Config/Importer/Processor/Update.php
index 87ecc18048ff6..35f3957b168d7 100644
--- a/app/code/Magento/Store/Model/Config/Importer/Processor/Update.php
+++ b/app/code/Magento/Store/Model/Config/Importer/Processor/Update.php
@@ -92,6 +92,10 @@ public function run(array $data)
];
foreach ($entities as $scope) {
+ if (!isset($data[$scope])) {
+ continue;
+ }
+
$items = $this->dataDifferenceCalculator->getItemsToUpdate($scope, $data[$scope]);
if (!$items) {
diff --git a/app/code/Magento/Store/Model/Config/Placeholder.php b/app/code/Magento/Store/Model/Config/Placeholder.php
index 3636390cf375f..b08e58c59c1a7 100644
--- a/app/code/Magento/Store/Model/Config/Placeholder.php
+++ b/app/code/Magento/Store/Model/Config/Placeholder.php
@@ -91,8 +91,8 @@ protected function _processPlaceholders($value, $data)
if ($url) {
$value = str_replace('{{' . $placeholder . '}}', $url, $value);
} elseif (strpos($value, $this->urlPlaceholder) !== false) {
- $distroBaseUrl = $this->request->getDistroBaseUrl();
- $value = str_replace($this->urlPlaceholder, $distroBaseUrl, $value);
+ // localhost is replaced for cli requests, for http requests method getDistroBaseUrl is used
+ $value = str_replace($this->urlPlaceholder, 'http://localhost/', $value);
}
if (null !== $this->_getPlaceholder($value)) {
diff --git a/app/code/Magento/Store/Model/Config/Validator.php b/app/code/Magento/Store/Model/Config/Validator.php
new file mode 100644
index 0000000000000..ea09434d37449
--- /dev/null
+++ b/app/code/Magento/Store/Model/Config/Validator.php
@@ -0,0 +1,38 @@
+ 0,
+ ScopeInterface::SCOPE_STORES => 'admin',
+ ScopeInterface::SCOPE_WEBSITES => 'admin'
+ ];
+ foreach ($entities as $scopeName => $key) {
+ if (empty($data[$scopeName])
+ || (count($data[$scopeName]) == 1 && isset($data[$scopeName][$key]))) {
+ return $errorMessage;
+ }
+ }
+ return [];
+ }
+}
diff --git a/app/code/Magento/Store/Test/Unit/App/Config/Source/InitialConfigSourceTest.php b/app/code/Magento/Store/Test/Unit/App/Config/Source/InitialConfigSourceTest.php
new file mode 100644
index 0000000000000..d63e626bc6bf8
--- /dev/null
+++ b/app/code/Magento/Store/Test/Unit/App/Config/Source/InitialConfigSourceTest.php
@@ -0,0 +1,124 @@
+readerMock = $this->getMockBuilder(Reader::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->deploymentConfigMock = $this->getMockBuilder(DeploymentConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->dataObjectFactory = $this->getMockBuilder(DataObjectFactory::class)
+ ->setMethods(['create'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->dataObjectMock = $this->getMockBuilder(DataObject::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->source = new InitialConfigSource(
+ $this->readerMock,
+ $this->deploymentConfigMock,
+ $this->dataObjectFactory,
+ 'configType'
+ );
+ }
+
+ /**
+ * @param string $path
+ * @param array $data
+ * @param string|array $expected
+ * @param string $expectedPath
+ * @dataProvider getDataProvider
+ */
+ public function testGet($path, $data, $expected, $expectedPath)
+ {
+ $this->readerMock->expects($this->once())
+ ->method('load')
+ ->willReturn($data);
+ $this->deploymentConfigMock->expects($this->once())
+ ->method('isAvailable')
+ ->willReturn(true);
+ $this->dataObjectFactory->expects($this->once())
+ ->method('create')
+ ->with(['data' => $data])
+ ->willReturn($this->dataObjectMock);
+ $this->dataObjectMock->expects($this->once())
+ ->method('getData')
+ ->with($expectedPath)
+ ->willReturn($expected);
+
+ $this->assertEquals($expected, $this->source->get($path));
+ }
+
+ /**
+ * @return array
+ */
+ public function getDataProvider()
+ {
+ return [
+ 'simple path' => ['path', ['configType' => 'value'], 'value', 'configType/path'],
+ 'empty path' => ['', [], [], 'configType'],
+ 'null path' => [null, [], [], 'configType'],
+ 'leading path' => ['/path', [], [], 'configType/path']
+ ];
+ }
+
+ public function testGetNotInstalled()
+ {
+ $path = 'path';
+
+ $this->readerMock->expects($this->never())
+ ->method('load');
+ $this->deploymentConfigMock->expects($this->once())
+ ->method('isAvailable')
+ ->willReturn(false);
+
+ $this->assertEquals([], $this->source->get($path));
+ }
+}
diff --git a/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/CreateTest.php b/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/CreateTest.php
index dfadaeb1dff7b..6fe2963e505da 100644
--- a/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/CreateTest.php
+++ b/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/CreateTest.php
@@ -19,6 +19,7 @@
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.TooManyFields)
*/
class CreateTest extends \PHPUnit_Framework_TestCase
{
@@ -52,16 +53,68 @@ class CreateTest extends \PHPUnit_Framework_TestCase
*/
private $abstractDbMock;
+ /**
+ * @var Website|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $websiteMock;
+
+ /**
+ * @var Group|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $groupMock;
+
+ /**
+ * @var Store|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $storeMock;
+
/**
* @var Create
*/
private $processor;
+ /**
+ * @var array
+ */
+ private $websites = [];
+
+ /**
+ * @var array
+ */
+ private $trimmedWebsite = [];
+
+ /**
+ * @var array
+ */
+ private $groups = [];
+
+ /**
+ * @var array
+ */
+ private $trimmedGroup = [];
+
+ /**
+ * @var array
+ */
+ private $stores = [];
+
+ /**
+ * @var array
+ */
+ private $trimmedStore = [];
+
+ /**
+ * @var array
+ */
+ private $data = [];
+
/**
* @inheritdoc
*/
protected function setUp()
{
+ $this->initTestData();
+
$this->dataDifferenceCalculatorMock = $this->getMockBuilder(DataDifferenceCalculator::class)
->disableOriginalConstructor()
->getMock();
@@ -83,6 +136,30 @@ protected function setUp()
->disableOriginalConstructor()
->setMethods(['save', 'load', 'addCommitCallback'])
->getMockForAbstractClass();
+ $this->websiteMock = $this->getMockBuilder(Website::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['setData', 'getResource', 'setDefaultGroupId'])
+ ->getMock();
+ $this->groupMock = $this->getMockBuilder(Group::class)
+ ->disableOriginalConstructor()
+ ->setMethods([
+ 'getResource', 'getId', 'setData', 'setRootCategoryId',
+ 'getDefaultStoreId', 'setDefaultStoreId', 'setWebsite'
+ ])
+ ->getMock();
+ $this->storeMock = $this->getMockBuilder(Store::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['setData', 'getResource', 'setGroup', 'setWebsite', 'getStoreId'])
+ ->getMock();
+ $this->websiteFactoryMock->expects($this->any())
+ ->method('create')
+ ->willReturn($this->websiteMock);
+ $this->groupFactoryMock->expects($this->any())
+ ->method('create')
+ ->willReturn($this->groupMock);
+ $this->storeFactoryMock->expects($this->any())
+ ->method('create')
+ ->willReturn($this->storeMock);
$this->processor = new Create(
$this->dataDifferenceCalculatorMock,
@@ -93,9 +170,9 @@ protected function setUp()
);
}
- public function testRunWebsite()
+ private function initTestData()
{
- $websites = [
+ $this->websites = [
'base' => [
'website_id' => '1',
'code' => 'base',
@@ -105,133 +182,154 @@ public function testRunWebsite()
'is_default' => '1',
],
];
- $trimmedWebsite = [
+ $this->trimmedWebsite = [
'code' => 'base',
'name' => 'Main Website',
'sort_order' => '0',
'is_default' => '1',
- 'default_group_id' => '1',
];
- $data = [
- 'websites' => $websites,
- 'groups' => [],
- 'stores' => [],
+ $this->groups = [
+ 1 => [
+ 'group_id' => '1',
+ 'website_id' => '1',
+ 'name' => 'Default',
+ 'root_category_id' => '1',
+ 'default_store_id' => '1',
+ 'code' => 'default',
+ ]
+ ];
+ $this->trimmedGroup = [
+ 'name' => 'Default',
+ 'root_category_id' => '1',
+ 'code' => 'default',
+ 'default_store_id' => '1',
+ ];
+ $this->stores = [
+ 'default' => [
+ 'store_id' => '1',
+ 'code' => 'default',
+ 'website_id' => '1',
+ 'group_id' => '1',
+ 'name' => 'Default Store View',
+ 'sort_order' => '0',
+ 'is_active' => '1',
+ ],
];
+ $this->trimmedStore = [
+ 'code' => 'default',
+ 'name' => 'Default Store View',
+ 'sort_order' => '0',
+ 'is_active' => '1',
+ ];
+ $this->data = [
+ 'websites' => $this->websites,
+ 'groups' => $this->groups,
+ 'stores' => $this->stores,
+ ];
+ }
+ public function testRunWebsite()
+ {
+ $groupId = 1;
$this->dataDifferenceCalculatorMock->expects($this->any())
->method('getItemsToCreate')
->willReturnMap([
- [ScopeInterface::SCOPE_WEBSITES, $websites, $websites],
+ [ScopeInterface::SCOPE_WEBSITES, $this->websites, $this->websites],
]);
- /** @var Website|\PHPUnit_Framework_MockObject_MockObject $websiteMock */
- $websiteMock = $this->getMockBuilder(Website::class)
- ->disableOriginalConstructor()
- ->setMethods(['setData', 'getResource'])
- ->getMock();
- $websiteMock->expects($this->once())
+ $this->websiteMock->expects($this->once())
->method('setData')
- ->with($trimmedWebsite)
+ ->with($this->trimmedWebsite)
->willReturnSelf();
- $websiteMock->expects($this->exactly(2))
+ $this->websiteMock->expects($this->exactly(3))
->method('getResource')
->willReturn($this->abstractDbMock);
+ $this->websiteMock->expects($this->once())
+ ->method('setDefaultGroupId')
+ ->with($groupId);
+
+ $this->groupMock->expects($this->once())
+ ->method('getResource')
+ ->willReturn($this->abstractDbMock);
+ $this->groupMock->expects($this->once())
+ ->method('getId')
+ ->willReturn($groupId);
- $this->websiteFactoryMock->expects($this->once())
- ->method('create')
- ->willReturn($websiteMock);
$this->abstractDbMock->expects($this->once())
+ ->method('addCommitCallback')
+ ->willReturnCallback(function ($function) {
+ return $function();
+ });
+
+ $this->abstractDbMock->expects($this->exactly(2))
->method('save')
- ->with($websiteMock)
+ ->with($this->websiteMock)
->willReturnSelf();
- $this->processor->run($data);
+ $this->processor->run($this->data);
}
public function testRunGroup()
{
- $websites = [
- 'base' => [
- 'website_id' => '1',
- 'code' => 'base',
- 'name' => 'Main Website',
- 'sort_order' => '0',
- 'default_group_id' => '1',
- 'is_default' => '1',
- ],
- ];
- $groups = [
- 1 => [
- 'group_id' => '1',
- 'website_id' => '1',
- 'name' => 'Default',
- 'root_category_id' => '1',
- 'default_store_id' => '1',
- 'code' => 'default',
- ]
- ];
- $trimmedGroup = [
- 'name' => 'Default',
- 'root_category_id' => '1',
- 'code' => 'default',
- 'default_store_id' => '1',
- ];
- $data = [
- 'websites' => $websites,
- 'groups' => $groups,
- 'stores' => [],
- ];
-
+ $defaultStoreId = 1;
+ $storeId = 1;
$this->dataDifferenceCalculatorMock->expects($this->any())
->method('getItemsToCreate')
->willReturnMap([
- [ScopeInterface::SCOPE_WEBSITES, $websites, []],
- [ScopeInterface::SCOPE_GROUPS, $groups, $groups],
+ [ScopeInterface::SCOPE_WEBSITES, $this->websites, []],
+ [ScopeInterface::SCOPE_GROUPS, $this->groups, $this->groups],
]);
- /** @var Website|\PHPUnit_Framework_MockObject_MockObject $websiteMock */
- $websiteMock = $this->getMockBuilder(Website::class)
- ->disableOriginalConstructor()
- ->setMethods(['getResource'])
- ->getMock();
- $websiteMock->expects($this->once())
+ $this->websiteMock->expects($this->once())
->method('getResource')
->willReturn($this->abstractDbMock);
- $this->websiteFactoryMock->expects($this->once())
- ->method('create')
- ->willReturn($websiteMock);
- $this->abstractDbMock->expects($this->once())
- ->method('load')
- ->with($websiteMock, 'base', 'code')
- ->willReturnSelf();
- /** @var Group|\PHPUnit_Framework_MockObject_MockObject $groupMock */
- $groupMock = $this->getMockBuilder(Group::class)
- ->disableOriginalConstructor()
- ->setMethods(['setData', 'getResource', 'setWebsite', 'setRootCategoryId'])
- ->getMock();
- $groupMock->expects($this->once())
+ $this->groupMock->expects($this->once())
->method('setData')
- ->with($trimmedGroup)
+ ->with($this->trimmedGroup)
->willReturnSelf();
- $groupMock->expects($this->exactly(2))
+ $this->groupMock->expects($this->exactly(3))
->method('getResource')
->willReturn($this->abstractDbMock);
- $groupMock->expects($this->once())
+ $this->groupMock->expects($this->once())
->method('setRootCategoryId')
->with(0);
+ $this->groupMock->expects($this->once())
+ ->method('getDefaultStoreId')
+ ->willReturn($defaultStoreId);
+ $this->groupMock->expects($this->once())
+ ->method('setDefaultStoreId')
+ ->with($storeId);
+ $this->groupMock->expects($this->once())
+ ->method('setWebsite')
+ ->with($this->websiteMock);
+
+ $this->storeMock->expects($this->once())
+ ->method('getResource')
+ ->willReturn($this->abstractDbMock);
+ $this->storeMock->expects($this->once())
+ ->method('getStoreId')
+ ->willReturn($storeId);
- $this->groupFactoryMock->expects($this->once())
- ->method('create')
- ->willReturn($groupMock);
- $this->abstractDbMock->expects($this->once())
+ $this->abstractDbMock->expects($this->any())
+ ->method('load')
+ ->withConsecutive([$this->websiteMock, 'base', 'code'], [$this->storeMock, 'default', 'code'])
+ ->willReturnSelf();
+ $this->abstractDbMock->expects($this->exactly(2))
->method('save')
- ->with($groupMock)
+ ->with($this->groupMock)
->willReturnSelf();
$this->abstractDbMock->expects($this->once())
- ->method('addCommitCallback');
+ ->method('addCommitCallback')
+ ->willReturnCallback(function ($function) {
+ return $function();
+ });
- $this->processor->run($data);
+ $this->eventManagerMock->expects($this->once())
+ ->method('dispatch')
+ ->with('store_group_save', ['group' => $this->groupMock]);
+
+ $this->processor->run($this->data);
}
/**
@@ -239,110 +337,56 @@ public function testRunGroup()
*/
public function testRunStore()
{
- $websites = [
- 'base' => [
- 'website_id' => '1',
- 'code' => 'base',
- 'name' => 'Main Website',
- 'sort_order' => '0',
- 'default_group_id' => '1',
- 'is_default' => '1',
- ],
- ];
- $groups = [
- 1 => [
- 'group_id' => '1',
- 'website_id' => '1',
- 'name' => 'Default',
- 'root_category_id' => '1',
- 'default_store_id' => '1',
- 'code' => 'default',
- ]
- ];
- $stores = [
- 'default' => [
- 'store_id' => '1',
- 'code' => 'default',
- 'website_id' => '1',
- 'group_id' => '1',
- 'name' => 'Default Store View',
- 'sort_order' => '0',
- 'is_active' => '1',
- ],
- ];
- $trimmedStore = [
- 'code' => 'default',
- 'name' => 'Default Store View',
- 'sort_order' => '0',
- 'is_active' => '1',
- ];
- $data = [
- 'websites' => $websites,
- 'groups' => $groups,
- 'stores' => $stores,
- ];
-
$this->dataDifferenceCalculatorMock->expects($this->any())
->method('getItemsToCreate')
->willReturnMap([
- [ScopeInterface::SCOPE_WEBSITES, $websites, []],
- [ScopeInterface::SCOPE_GROUPS, $groups, []],
- [ScopeInterface::SCOPE_STORES, $stores, $stores],
+ [ScopeInterface::SCOPE_WEBSITES, $this->websites, []],
+ [ScopeInterface::SCOPE_GROUPS, $this->groups, []],
+ [ScopeInterface::SCOPE_STORES, $this->stores, $this->stores],
]);
- /** @var Website|\PHPUnit_Framework_MockObject_MockObject $websiteMock */
- $websiteMock = $this->getMockBuilder(Website::class)
- ->disableOriginalConstructor()
- ->setMethods(['getResource'])
- ->getMock();
- $websiteMock->expects($this->once())
+ $this->websiteMock->expects($this->once())
->method('getResource')
->willReturn($this->abstractDbMock);
- $this->websiteFactoryMock->expects($this->once())
- ->method('create')
- ->willReturn($websiteMock);
- /** @var Group|\PHPUnit_Framework_MockObject_MockObject $groupMock */
- $groupMock = $this->getMockBuilder(Group::class)
- ->disableOriginalConstructor()
- ->setMethods(['getResource'])
- ->getMock();
- $groupMock->expects($this->once())
+ $this->groupMock->expects($this->once())
->method('getResource')
->willReturn($this->abstractDbMock);
- $this->groupFactoryMock->expects($this->once())
- ->method('create')
- ->willReturn($groupMock);
$this->abstractDbMock->expects($this->exactly(2))
->method('load')
- ->withConsecutive([$groupMock, 'default', 'code'], [$websiteMock, 'base', 'code'])
+ ->withConsecutive([$this->groupMock, 'default', 'code'], [$this->websiteMock, 'base', 'code'])
->willReturnSelf();
- /** @var Store|\PHPUnit_Framework_MockObject_MockObject $storeMock */
- $storeMock = $this->getMockBuilder(Store::class)
- ->disableOriginalConstructor()
- ->setMethods(['setData', 'getResource', 'setGroup', 'setWebsite'])
- ->getMock();
- $storeMock->expects($this->once())
+ $this->storeMock->expects($this->once())
->method('setData')
- ->with($trimmedStore)
+ ->with($this->trimmedStore)
->willReturnSelf();
- $storeMock->expects($this->exactly(2))
+ $this->storeMock->expects($this->exactly(3))
->method('getResource')
->willReturn($this->abstractDbMock);
+ $this->storeMock->expects($this->once())
+ ->method('setGroup')
+ ->with($this->groupMock);
+ $this->storeMock->expects($this->once())
+ ->method('setWebsite')
+ ->with($this->websiteMock);
- $this->storeFactoryMock->expects($this->once())
- ->method('create')
- ->willReturn($storeMock);
- $this->abstractDbMock->expects($this->once())
+ $this->abstractDbMock->expects($this->exactly(2))
->method('save')
- ->with($storeMock)
+ ->with($this->storeMock)
->willReturnSelf();
$this->abstractDbMock->expects($this->once())
- ->method('addCommitCallback');
+ ->method('addCommitCallback')
+ ->willReturnCallback(function ($function) {
+ return $function();
+ });
- $this->processor->run($data);
+ $this->eventManagerMock->expects($this->once())
+ ->method('dispatch')
+ ->with('store_add', ['store' => $this->storeMock]);
+
+ $this->processor->run($this->data);
}
/**
diff --git a/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/DeleteTest.php b/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/DeleteTest.php
index d11139d559fc1..b878de90ffcbd 100644
--- a/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/DeleteTest.php
+++ b/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/DeleteTest.php
@@ -315,7 +315,6 @@ public function testRunNothingToDelete()
public function testRunWithException()
{
$data = [
- ScopeInterface::SCOPE_GROUPS => [],
ScopeInterface::SCOPE_WEBSITES => [],
ScopeInterface::SCOPE_STORES => []
];
diff --git a/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/UpdateTest.php b/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/UpdateTest.php
index be22afab0f445..086edd2d2d74e 100644
--- a/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/UpdateTest.php
+++ b/app/code/Magento/Store/Test/Unit/Model/Config/Importer/Processor/UpdateTest.php
@@ -298,7 +298,6 @@ public function testRunWithException()
{
$data = [
ScopeInterface::SCOPE_GROUPS => [],
- ScopeInterface::SCOPE_WEBSITES => [],
ScopeInterface::SCOPE_STORES => []
];
diff --git a/app/code/Magento/Store/Test/Unit/Model/Config/ImporterTest.php b/app/code/Magento/Store/Test/Unit/Model/Config/ImporterTest.php
index 17a488bcb780f..f303e29aec43a 100644
--- a/app/code/Magento/Store/Test/Unit/Model/Config/ImporterTest.php
+++ b/app/code/Magento/Store/Test/Unit/Model/Config/ImporterTest.php
@@ -76,9 +76,6 @@ protected function setUp()
$this->resourceMock = $this->getMockBuilder(Website::class)
->disableOriginalConstructor()
->getMock();
- $this->processorFactoryMock->expects($this->any())
- ->method('create')
- ->willReturn($this->processorMock);
$this->model = new Importer(
$this->dataDifferenceCalculatorMock,
@@ -97,9 +94,30 @@ public function testImport()
ScopeInterface::SCOPE_WEBSITES => ['websites'],
];
+ $createProcessorMock = clone $this->processorMock;
+ $deleteProcessorMock = clone $this->processorMock;
+ $updateProcessorMock = clone $this->processorMock;
+
+ $this->processorFactoryMock->expects($this->exactly(3))
+ ->method('create')
+ ->withConsecutive(
+ [Importer\Processor\ProcessorFactory::TYPE_CREATE],
+ [Importer\Processor\ProcessorFactory::TYPE_DELETE],
+ [Importer\Processor\ProcessorFactory::TYPE_UPDATE]
+ )->willReturnOnConsecutiveCalls(
+ $createProcessorMock,
+ $deleteProcessorMock,
+ $updateProcessorMock
+ );
$this->resourceMock->expects($this->once())
->method('beginTransaction');
- $this->processorMock->expects($this->exactly(3))
+ $createProcessorMock->expects($this->once())
+ ->method('run')
+ ->with($data);
+ $deleteProcessorMock->expects($this->once())
+ ->method('run')
+ ->with($data);
+ $updateProcessorMock->expects($this->once())
->method('run')
->with($data);
$this->resourceMock->expects($this->once())
@@ -133,6 +151,9 @@ public function testImport()
*/
public function testImportWithException()
{
+ $this->processorFactoryMock->expects($this->any())
+ ->method('create')
+ ->willReturn($this->processorMock);
$this->resourceMock->expects($this->once())
->method('beginTransaction');
$this->processorMock->expects($this->any())
diff --git a/app/code/Magento/Store/Test/Unit/Model/Config/ValidatorTest.php b/app/code/Magento/Store/Test/Unit/Model/Config/ValidatorTest.php
new file mode 100644
index 0000000000000..5e277f11317cb
--- /dev/null
+++ b/app/code/Magento/Store/Test/Unit/Model/Config/ValidatorTest.php
@@ -0,0 +1,91 @@
+assertEquals($result, $model->validate($data));
+ }
+
+ /**
+ * @return array
+ */
+ public function validateDataProvider()
+ {
+ $errorMessage = 'Scopes data should have at least one not admin website, group and store.';
+ return [
+ [
+ [],
+ [$errorMessage]
+ ],
+ [
+ [
+ ScopeInterface::SCOPE_GROUPS => [],
+ ScopeInterface::SCOPE_STORES => [],
+ ],
+ [$errorMessage]
+ ],
+ [
+ [
+ ScopeInterface::SCOPE_GROUPS => [0 => ['name' => 'group one']],
+ ScopeInterface::SCOPE_STORES => ['admin' => ['name' => 'admin store']],
+ ScopeInterface::SCOPE_WEBSITES => ['admin' => ['name' => 'admin website']]
+ ],
+ [$errorMessage]
+ ],
+ [
+ [
+ ScopeInterface::SCOPE_GROUPS => [
+ 0 => ['name' => 'group one'],
+ 1 => ['name' => 'group two']
+ ],
+ ScopeInterface::SCOPE_STORES => [
+ 'admin' => ['name' => 'admin store'],
+ 'store-two' => ['name' => 'store two'],
+ ],
+ ScopeInterface::SCOPE_WEBSITES => [
+ 'admin' => ['name' => 'admin website']
+ ]
+ ],
+ [$errorMessage]
+ ],
+ [
+ [
+ ScopeInterface::SCOPE_GROUPS => [
+ 0 => ['name' => 'group one'],
+ 1 => ['name' => 'group two']
+ ],
+ ScopeInterface::SCOPE_STORES => [
+ 'admin' => ['name' => 'admin store'],
+ 'store-two' => ['name' => 'store two'],
+ ],
+ ScopeInterface::SCOPE_WEBSITES => [
+ 'admin' => ['name' => 'admin website'],
+ 'website-two' => ['name' => 'website two'],
+ ]
+ ],
+ []
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Store/etc/di.xml b/app/code/Magento/Store/etc/di.xml
index c9224b7230f57..f88ecc889f949 100644
--- a/app/code/Magento/Store/etc/di.xml
+++ b/app/code/Magento/Store/etc/di.xml
@@ -346,11 +346,10 @@
-
+
Magento\Framework\App\DeploymentConfig\Reader
Magento\Store\App\Config\Type\Scopes::CONFIG_TYPE
- Magento\Framework\Config\File\ConfigFilePool::APP_CONFIG
@@ -375,7 +374,9 @@
-
-
- Magento\Store\Model\Config\Importer
+ - Magento\Store\Model\Config\Importer
+ - Magento\Store\Model\Config\Validator
+ - 10
diff --git a/app/code/Magento/Theme/etc/di.xml b/app/code/Magento/Theme/etc/di.xml
index 813a8b858b99f..16d96862c8f7d 100644
--- a/app/code/Magento/Theme/etc/di.xml
+++ b/app/code/Magento/Theme/etc/di.xml
@@ -248,8 +248,8 @@
-
-
- Magento\Theme\Model\Config\Importer
- - 20
+ - Magento\Theme\Model\Config\Importer
+ - 20
diff --git a/app/design/frontend/Magento/blank/web/css/source/_email-base.less b/app/design/frontend/Magento/blank/web/css/source/_email-base.less
index 1cdae848ec318..1d7336b8b3222 100644
--- a/app/design/frontend/Magento/blank/web/css/source/_email-base.less
+++ b/app/design/frontend/Magento/blank/web/css/source/_email-base.less
@@ -287,3 +287,19 @@ body {
}
}
}
+
+.message-details {
+ margin-bottom: @indent__s;
+
+ b {
+ font-weight: bold;
+ }
+
+ td {
+ padding-bottom: @indent__xs;
+
+ b {
+ margin-right: @indent__s;
+ }
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserEntityTest.php b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserEntityTest.php
index b7fb5d5a7d9f9..e103615a5fef6 100644
--- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserEntityTest.php
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserEntityTest.php
@@ -35,6 +35,7 @@ class UpdateAdminUserEntityTest extends Injectable
/* tags */
const MVP = 'no';
const TEST_TYPE = 'acceptance_test, extended_acceptance_test';
+ const SEVERITY = 'S3';
/* end tags */
/**
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserEntityTest.xml b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserEntityTest.xml
index 79ee73e2a1836..d53b9de28a53b 100644
--- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserEntityTest.xml
@@ -8,7 +8,7 @@
- MAGETWO-65658: [FT] User with restricted access can't log in to Admin in UpdateAdminUserEntityTest
+ severity:S3
custom_admin_with_default_role
role::role_sales
123123q
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/etc/curl/di.xml b/dev/tests/functional/tests/app/Magento/User/Test/etc/curl/di.xml
index 18f6c70b8f3db..d0af1d0dc3f66 100644
--- a/dev/tests/functional/tests/app/Magento/User/Test/etc/curl/di.xml
+++ b/dev/tests/functional/tests/app/Magento/User/Test/etc/curl/di.xml
@@ -8,4 +8,29 @@
+
+
+ S3
+
+
+
+
+ S3
+
+
+
+
+ S3
+
+
+
+
+ S3
+
+
+
+
+ S3
+
+
diff --git a/dev/tests/integration/testsuite/Magento/Config/App/Config/Type/SystemTest.php b/dev/tests/integration/testsuite/Magento/Config/App/Config/Type/SystemTest.php
new file mode 100644
index 0000000000000..9ab436ad077ca
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Config/App/Config/Type/SystemTest.php
@@ -0,0 +1,50 @@
+objectManager = Bootstrap::getObjectManager();
+ $this->system = $this->objectManager->create(System::class);
+ }
+
+ public function testGetValueDefaultScope()
+ {
+ $this->assertEquals(
+ 'value1.db.default.test',
+ $this->system->get('default/web/test/test_value_1')
+ );
+
+ $this->assertEquals(
+ 'value1.db.website_base.test',
+ $this->system->get('websites/base/web/test/test_value_1')
+ );
+
+ $this->assertEquals(
+ 'value1.db.store_default.test',
+ $this->system->get('stores/default/web/test/test_value_1')
+ );
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/ConfigImportCommandTest.php b/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/ConfigImportCommandTest.php
index 12cbda7f3ea98..690045d67b930 100644
--- a/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/ConfigImportCommandTest.php
+++ b/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/ConfigImportCommandTest.php
@@ -226,6 +226,33 @@ public function testImportStores()
$this->assertSame(null, $group->getId());
}
+ /**
+ * @magentoDbIsolation disabled
+ */
+ public function testImportStoresWithWrongConfiguration()
+ {
+ $this->assertEmpty($this->hash->get());
+
+ $dumpCommand = $this->objectManager->create(ApplicationDumpCommand::class);
+ $dumpCommandTester = new CommandTester($dumpCommand);
+ $dumpCommandTester->execute([]);
+ $dumpedData = $this->reader->load(ConfigFilePool::APP_CONFIG);
+
+ unset($dumpedData['scopes']['websites']['base']);
+
+ $this->writeConfig($dumpedData, []);
+
+ $importCommand = $this->objectManager->create(ConfigImportCommand::class);
+ $importCommandTester = new CommandTester($importCommand);
+ $importCommandTester->execute([]);
+
+ $this->assertContains(
+ 'Scopes data should have at least one not admin website, group and store.',
+ $importCommandTester->getDisplay()
+ );
+ $this->assertSame(Cli::RETURN_FAILURE, $importCommandTester->getStatusCode());
+ }
+
/**
* @magentoDbIsolation enabled
*/
diff --git a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php
index 62b97e54bf0f0..e4ab0aa99fa44 100644
--- a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php
@@ -14,6 +14,11 @@
*/
class ProductTest extends \PHPUnit_Framework_TestCase
{
+ /**
+ * Base product image path
+ */
+ const BASE_IMAGE_PATH = 'http://localhost/pub/media/catalog/product/cache/c9e0b0ef589f3508e5ba515cde53c5ff';
+
/**
* Test getCollection None images
* 1) Check that image attributes were not loaded
@@ -72,7 +77,7 @@ public function testGetCollectionAll()
$this->assertNotEmpty($products[4]->getImages(), 'Images were not loaded');
$this->assertEquals('Simple Images', $products[4]->getImages()->getTitle(), 'Incorrect title');
$this->assertEquals(
- 'catalog/product/m/a/magento_image_sitemap.png',
+ self::BASE_IMAGE_PATH.'/m/a/magento_image_sitemap.png',
$products[4]->getImages()->getThumbnail(),
'Incorrect thumbnail'
);
@@ -80,12 +85,12 @@ public function testGetCollectionAll()
$imagesCollection = $products[4]->getImages()->getCollection();
$this->assertEquals(
- 'catalog/product/m/a/magento_image_sitemap.png',
+ self::BASE_IMAGE_PATH.'/m/a/magento_image_sitemap.png',
$imagesCollection[0]->getUrl(),
'Incorrect image url'
);
$this->assertEquals(
- 'catalog/product/s/e/second_image.png',
+ self::BASE_IMAGE_PATH.'/s/e/second_image.png',
$imagesCollection[1]->getUrl(),
'Incorrect image url'
);
@@ -97,12 +102,12 @@ public function testGetCollectionAll()
$imagesCollection = $products[5]->getImages()->getCollection();
$this->assertCount(1, $imagesCollection);
$this->assertEquals(
- 'catalog/product/s/e/second_image_1.png',
+ self::BASE_IMAGE_PATH.'/s/e/second_image_1.png',
$imagesCollection[0]->getUrl(),
'Image url is incorrect'
);
$this->assertEquals(
- 'catalog/product/s/e/second_image_1.png',
+ self::BASE_IMAGE_PATH.'/s/e/second_image_1.png',
$products[5]->getImages()->getThumbnail(),
'Product thumbnail is incorrect'
);
@@ -140,7 +145,7 @@ public function testGetCollectionBase()
$this->assertNotEmpty($products[4]->getImages(), 'Images were not loaded');
$this->assertEquals('Simple Images', $products[4]->getImages()->getTitle(), 'Incorrect title');
$this->assertEquals(
- 'catalog/product/s/e/second_image.png',
+ self::BASE_IMAGE_PATH.'/s/e/second_image.png',
$products[4]->getImages()->getThumbnail(),
'Incorrect thumbnail'
);
@@ -148,7 +153,7 @@ public function testGetCollectionBase()
$imagesCollection = $products[4]->getImages()->getCollection();
$this->assertEquals(
- 'catalog/product/s/e/second_image.png',
+ self::BASE_IMAGE_PATH.'/s/e/second_image.png',
$imagesCollection[0]->getUrl(),
'Incorrect image url'
);
diff --git a/lib/internal/Magento/Framework/App/DeploymentConfig/ValidatorInterface.php b/lib/internal/Magento/Framework/App/DeploymentConfig/ValidatorInterface.php
new file mode 100644
index 0000000000000..aaa31f018320a
--- /dev/null
+++ b/lib/internal/Magento/Framework/App/DeploymentConfig/ValidatorInterface.php
@@ -0,0 +1,21 @@
+exceptionHandler->handle($record);
- } else {
- unset($record['context']['is_exception']);
- $record['formatted'] = $this->getFormatter()->format($record);
- parent::write($record);
+
+ return;
}
+
+ $record['formatted'] = $this->getFormatter()->format($record);
+
+ parent::write($record);
}
}
diff --git a/lib/internal/Magento/Framework/Logger/Monolog.php b/lib/internal/Magento/Framework/Logger/Monolog.php
index 1a07a5e317bbf..f7647605cb7ac 100644
--- a/lib/internal/Magento/Framework/Logger/Monolog.php
+++ b/lib/internal/Magento/Framework/Logger/Monolog.php
@@ -33,7 +33,18 @@ public function __construct($name, array $handlers = [], array $processors = [])
*/
public function addRecord($level, $message, array $context = [])
{
- $context['is_exception'] = $message instanceof \Exception;
+ /**
+ * To preserve compatibility with Exception messages.
+ * And support PSR-3 context standard.
+ *
+ * @link http://www.php-fig.org/psr/psr-3/#context PSR-3 context standard
+ */
+ if ($message instanceof \Exception && !isset($context['exception'])) {
+ $context['exception'] = $message;
+ }
+
+ $message = $message instanceof \Exception ? $message->getMessage() : $message;
+
return parent::addRecord($level, $message, $context);
}
}
diff --git a/lib/internal/Magento/Framework/Logger/Test/Unit/Handler/SystemTest.php b/lib/internal/Magento/Framework/Logger/Test/Unit/Handler/SystemTest.php
new file mode 100644
index 0000000000000..d621551837b68
--- /dev/null
+++ b/lib/internal/Magento/Framework/Logger/Test/Unit/Handler/SystemTest.php
@@ -0,0 +1,91 @@
+filesystemMock = $this->getMockBuilder(DriverInterface::class)
+ ->getMockForAbstractClass();
+ $this->exceptionHandlerMock = $this->getMockBuilder(Exception::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->model = new System(
+ $this->filesystemMock,
+ $this->exceptionHandlerMock
+ );
+ }
+
+ public function testWrite()
+ {
+ $this->filesystemMock->expects($this->once())
+ ->method('getParentDirectory');
+ $this->filesystemMock->expects($this->once())
+ ->method('isDirectory')
+ ->willReturn('true');
+
+ $this->model->write($this->getRecord());
+ }
+
+ public function testWriteException()
+ {
+ $record = $this->getRecord();
+ $record['context']['exception'] = new \Exception('Some exception');
+
+ $this->exceptionHandlerMock->expects($this->once())
+ ->method('handle')
+ ->with($record);
+ $this->filesystemMock->expects($this->never())
+ ->method('getParentDirectory');
+
+ $this->model->write($record);
+ }
+
+ /**
+ * @param int $level
+ * @param string $message
+ * @param array $context
+ * @return array
+ */
+ private function getRecord($level = Logger::WARNING, $message = 'test', $context = [])
+ {
+ return [
+ 'message' => $message,
+ 'context' => $context,
+ 'level' => $level,
+ 'level_name' => Logger::getLevelName($level),
+ 'channel' => 'test',
+ 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))),
+ 'extra' => [],
+ ];
+ }
+}
diff --git a/lib/internal/Magento/Framework/Logger/Test/Unit/MonologTest.php b/lib/internal/Magento/Framework/Logger/Test/Unit/MonologTest.php
new file mode 100644
index 0000000000000..e6c47b13d2a9d
--- /dev/null
+++ b/lib/internal/Magento/Framework/Logger/Test/Unit/MonologTest.php
@@ -0,0 +1,39 @@
+pushHandler($handler);
+
+ $logger->addError('test');
+ list($record) = $handler->getRecords();
+
+ $this->assertSame('test', $record['message']);
+ }
+
+ public function testAddRecordAsException()
+ {
+ $logger = new Monolog(__METHOD__);
+ $handler = new TestHandler();
+
+ $logger->pushHandler($handler);
+
+ $logger->addError(new \Exception('Some exception'));
+ list($record) = $handler->getRecords();
+
+ $this->assertInstanceOf(\Exception::class, $record['context']['exception']);
+ $this->assertSame('Some exception', $record['message']);
+ }
+}