@@ -145,11 +152,9 @@ require([
action: 'load',
fileTypes: /^image\/(gif|jpeg|png)$/,
maxFileSize: = (int) $block->getFileSizeService()->getMaxFileSize() ?> * 10
- }, {
- action: 'resize',
- maxWidth: = /* @escapeNotVerified */ $block->getImageUploadMaxWidth() ?> ,
- maxHeight: = /* @escapeNotVerified */ $block->getImageUploadMaxHeight() ?>
- }, {
+ },
+ = /* @noEscape */ $resizeConfig ?>,
+ {
action: 'save'
}]
});
diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml
index f4698b6865779..8a56c2777084e 100644
--- a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml
+++ b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml
@@ -8,5 +8,8 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
index 19de63b7a976c..f98075f2294cc 100644
--- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
+++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
@@ -453,6 +453,10 @@ public function getConfigurableAttributes($product)
['group' => 'CONFIGURABLE', 'method' => __METHOD__]
);
if (!$product->hasData($this->_configurableAttributes)) {
+ // for new product do not load configurable attributes
+ if (!$product->getId()) {
+ return [];
+ }
$configurableAttributes = $this->getConfigurableAttributeCollection($product);
$this->extensionAttributesJoinProcessor->process($configurableAttributes);
$configurableAttributes->orderByPosition()->load();
@@ -1398,23 +1402,47 @@ private function getConfiguredUsedProductCollection(
$skipStockFilter = true
) {
$collection = $this->getUsedProductCollection($product);
+
if ($skipStockFilter) {
$collection->setFlag('has_stock_status_filter', true);
}
+
$collection
- ->addAttributeToSelect($this->getCatalogConfig()->getProductAttributes())
+ ->addAttributeToSelect($this->getAttributesForCollection($product))
->addFilterByRequiredOptions()
->setStoreId($product->getStoreId());
- $requiredAttributes = ['name', 'price', 'weight', 'image', 'thumbnail', 'status', 'media_gallery'];
- foreach ($requiredAttributes as $attributeCode) {
- $collection->addAttributeToSelect($attributeCode);
- }
- foreach ($this->getUsedProductAttributes($product) as $usedProductAttribute) {
- $collection->addAttributeToSelect($usedProductAttribute->getAttributeCode());
- }
$collection->addMediaGalleryData();
$collection->addTierPriceData();
+
return $collection;
}
+
+ /**
+ * @return array
+ */
+ private function getAttributesForCollection(\Magento\Catalog\Model\Product $product)
+ {
+ $productAttributes = $this->getCatalogConfig()->getProductAttributes();
+
+ $requiredAttributes = [
+ 'name',
+ 'price',
+ 'weight',
+ 'image',
+ 'thumbnail',
+ 'status',
+ 'visibility',
+ 'media_gallery'
+ ];
+
+ $usedAttributes = array_map(
+ function($attr) {
+ return $attr->getAttributeCode();
+ },
+ $this->getUsedProductAttributes($product)
+ );
+
+ return array_unique(array_merge($productAttributes, $requiredAttributes, $usedAttributes));
+ }
}
diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable.php
index ccff85dd9717f..feffd22a0fb3d 100644
--- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable.php
+++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable.php
@@ -19,6 +19,9 @@
use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Adapter\AdapterInterface;
+/**
+ * Configurable product resource model.
+ */
class Configurable extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
/**
@@ -173,10 +176,13 @@ public function getChildrenIds($parentId, $required = true)
$parentId
);
- $childrenIds = [0 => []];
- foreach ($this->getConnection()->fetchAll($select) as $row) {
- $childrenIds[0][$row['product_id']] = $row['product_id'];
- }
+ $childrenIds = [
+ 0 => array_column(
+ $this->getConnection()->fetchAll($select),
+ 'product_id',
+ 'product_id'
+ )
+ ];
return $childrenIds;
}
diff --git a/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php b/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php
index efddb278df36c..df8782ae422b4 100644
--- a/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php
+++ b/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php
@@ -27,8 +27,7 @@ public function __construct(
}
/**
- * Performs an additional check whether given configurable product has
- * at least one configuration in-stock.
+ * Performs an additional check whether given configurable product has at least one configuration in-stock.
*
* @param \Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolver $subject
* @param bool $result
@@ -44,9 +43,7 @@ public function afterIsSalable(
\Magento\Framework\Pricing\SaleableInterface $salableItem
) {
if ($salableItem->getTypeId() == 'configurable' && $result) {
- if (!$this->lowestPriceOptionsProvider->getProducts($salableItem)) {
- $result = false;
- }
+ $result = $salableItem->isSalable();
}
return $result;
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml
index 99e47baac37d5..7901b6f2290c9 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml
@@ -35,5 +35,6 @@
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php
index 5e9399ddd3d65..d1cf77f03a7bd 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php
+++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php
@@ -27,6 +27,7 @@
* @SuppressWarnings(PHPMD.LongVariable)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @SuppressWarnings(PHPMD.TooManyFields)
+ * @codingStandardsIgnoreFile
*/
class ConfigurableTest extends \PHPUnit\Framework\TestCase
{
@@ -154,8 +155,7 @@ protected function setUp()
->setMethods(['create'])
->getMock();
$this->productCollectionFactory = $this->getMockBuilder(
- \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory::class
- )
+ \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory::class)
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
@@ -197,11 +197,6 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
- $this->productFactory = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterfaceFactory::class)
- ->setMethods(['create'])
- ->disableOriginalConstructor()
- ->getMock();
-
$this->salableProcessor = $this->createMock(SalableProcessor::class);
$this->model = $this->objectHelper->getObject(
@@ -287,8 +282,7 @@ public function testSave()
->method('getData')
->willReturnMap($dataMap);
$attribute = $this->getMockBuilder(
- \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class
- )
+ \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class)
->disableOriginalConstructor()
->setMethods(['addData', 'setStoreId', 'setProductId', 'save', '__wakeup', '__sleep'])
->getMock();
@@ -385,7 +379,7 @@ public function testGetUsedProducts()
['_cache_instance_used_product_attributes', null, []]
]
);
-
+ $this->catalogConfig->expects($this->any())->method('getProductAttributes')->willReturn([]);
$productCollection->expects($this->atLeastOnce())->method('addAttributeToSelect')->willReturnSelf();
$productCollection->expects($this->once())->method('setProductFilter')->willReturnSelf();
$productCollection->expects($this->atLeastOnce())->method('setFlag')->willReturnSelf();
@@ -471,8 +465,7 @@ public function testGetConfigurableAttributesAsArray($productStore)
$eavAttribute->expects($this->atLeastOnce())->method('getStoreLabel')->willReturn('Store Label');
$attribute = $this->getMockBuilder(
- \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class
- )
+ \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class)
->disableOriginalConstructor()
->setMethods(['getProductAttribute', '__wakeup', '__sleep'])
->getMock();
@@ -515,17 +508,34 @@ public function getConfigurableAttributesAsArrayDataProvider()
];
}
- public function testGetConfigurableAttributes()
+ public function testGetConfigurableAttributesNewProduct()
{
$configurableAttributes = '_cache_instance_configurable_attributes';
/** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $product */
$product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
- ->setMethods(['getData', 'hasData', 'setData'])
+ ->setMethods(['hasData', 'getId'])
->disableOriginalConstructor()
->getMock();
$product->expects($this->once())->method('hasData')->with($configurableAttributes)->willReturn(false);
+ $product->expects($this->once())->method('getId')->willReturn(null);
+
+ $this->assertEquals([], $this->model->getConfigurableAttributes($product));
+ }
+
+ public function testGetConfigurableAttributes()
+ {
+ $configurableAttributes = '_cache_instance_configurable_attributes';
+
+ /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $product */
+ $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
+ ->setMethods(['getData', 'hasData', 'setData', 'getId'])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $product->expects($this->once())->method('hasData')->with($configurableAttributes)->willReturn(false);
+ $product->expects($this->once())->method('getId')->willReturn(1);
$attributeCollection = $this->getMockBuilder(Collection::class)
->setMethods(['setProductFilter', 'orderByPosition', 'load'])
@@ -582,8 +592,7 @@ public function testHasOptionsConfigurableAttribute()
->disableOriginalConstructor()
->getMock();
$attributeMock = $this->getMockBuilder(
- \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class
- )
+ \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute::class)
->disableOriginalConstructor()
->getMock();
diff --git a/app/code/Magento/Customer/Controller/Section/Load.php b/app/code/Magento/Customer/Controller/Section/Load.php
index e55b1d0df26c7..37cd071b13623 100644
--- a/app/code/Magento/Customer/Controller/Section/Load.php
+++ b/app/code/Magento/Customer/Controller/Section/Load.php
@@ -59,7 +59,7 @@ public function __construct(
}
/**
- * @return \Magento\Framework\Controller\Result\Json
+ * @inheritdoc
*/
public function execute()
{
@@ -71,11 +71,11 @@ public function execute()
$sectionNames = $this->getRequest()->getParam('sections');
$sectionNames = $sectionNames ? array_unique(\explode(',', $sectionNames)) : null;
- $updateSectionId = $this->getRequest()->getParam('update_section_id');
- if ('false' === $updateSectionId) {
- $updateSectionId = false;
+ $forceNewSectionTimestamp = $this->getRequest()->getParam('force_new_section_timestamp');
+ if ('false' === $forceNewSectionTimestamp) {
+ $forceNewSectionTimestamp = false;
}
- $response = $this->sectionPool->getSectionsData($sectionNames, (bool)$updateSectionId);
+ $response = $this->sectionPool->getSectionsData($sectionNames, (bool)$forceNewSectionTimestamp);
} catch (\Exception $e) {
$resultJson->setStatusHeader(
\Zend\Http\Response::STATUS_CODE_400,
diff --git a/app/code/Magento/Customer/CustomerData/Section/Identifier.php b/app/code/Magento/Customer/CustomerData/Section/Identifier.php
index 2a770925d1c37..a8bc2c8abc11a 100644
--- a/app/code/Magento/Customer/CustomerData/Section/Identifier.php
+++ b/app/code/Magento/Customer/CustomerData/Section/Identifier.php
@@ -43,12 +43,12 @@ public function __construct(
/**
* Init mark(identifier) for sections
*
- * @param bool $forceUpdate
+ * @param bool $forceNewTimestamp
* @return int
*/
- public function initMark($forceUpdate)
+ public function initMark($forceNewTimestamp)
{
- if ($forceUpdate) {
+ if ($forceNewTimestamp) {
$this->markId = time();
return $this->markId;
}
@@ -67,19 +67,19 @@ public function initMark($forceUpdate)
* Mark sections with data id
*
* @param array $sectionsData
- * @param null $sectionNames
- * @param bool $updateIds
+ * @param array|null $sectionNames
+ * @param bool $forceNewTimestamp
* @return array
*/
- public function markSections(array $sectionsData, $sectionNames = null, $updateIds = false)
+ public function markSections(array $sectionsData, $sectionNames = null, $forceNewTimestamp = false)
{
if (!$sectionNames) {
$sectionNames = array_keys($sectionsData);
}
- $markId = $this->initMark($updateIds);
+ $markId = $this->initMark($forceNewTimestamp);
foreach ($sectionNames as $name) {
- if ($updateIds || !array_key_exists(self::SECTION_KEY, $sectionsData[$name])) {
+ if ($forceNewTimestamp || !array_key_exists(self::SECTION_KEY, $sectionsData[$name])) {
$sectionsData[$name][self::SECTION_KEY] = $markId;
}
}
diff --git a/app/code/Magento/Customer/CustomerData/SectionPool.php b/app/code/Magento/Customer/CustomerData/SectionPool.php
index 0e0d7b992e33a..efea1762d9de6 100644
--- a/app/code/Magento/Customer/CustomerData/SectionPool.php
+++ b/app/code/Magento/Customer/CustomerData/SectionPool.php
@@ -53,12 +53,12 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
- public function getSectionsData(array $sectionNames = null, $updateIds = false)
+ public function getSectionsData(array $sectionNames = null, $forceNewTimestamp = false)
{
$sectionsData = $sectionNames ? $this->getSectionDataByNames($sectionNames) : $this->getAllSectionData();
- $sectionsData = $this->identifier->markSections($sectionsData, $sectionNames, $updateIds);
+ $sectionsData = $this->identifier->markSections($sectionsData, $sectionNames, $forceNewTimestamp);
return $sectionsData;
}
diff --git a/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php b/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php
index c308804fd0f8d..ad73b9722b133 100644
--- a/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php
+++ b/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php
@@ -14,8 +14,8 @@ interface SectionPoolInterface
* Get section data by section names. If $sectionNames is null then return all sections data
*
* @param array $sectionNames
- * @param bool $updateIds
+ * @param bool $forceNewTimestamp
* @return array
*/
- public function getSectionsData(array $sectionNames = null, $updateIds = false);
+ public function getSectionsData(array $sectionNames = null, $forceNewTimestamp = false);
}
diff --git a/app/code/Magento/Customer/Model/Indexer/Source.php b/app/code/Magento/Customer/Model/Indexer/Source.php
index e4bf03e08a9ad..a8878e2084ea0 100644
--- a/app/code/Magento/Customer/Model/Indexer/Source.php
+++ b/app/code/Magento/Customer/Model/Indexer/Source.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Customer\Model\Indexer;
+use Magento\Customer\Model\ResourceModel\Customer\Indexer\CollectionFactory;
use Magento\Customer\Model\ResourceModel\Customer\Indexer\Collection;
use Magento\Framework\App\ResourceConnection\SourceProviderInterface;
use Traversable;
@@ -25,11 +26,11 @@ class Source implements \IteratorAggregate, \Countable, SourceProviderInterface
private $batchSize;
/**
- * @param \Magento\Customer\Model\ResourceModel\Customer\Indexer\CollectionFactory $collection
+ * @param CollectionFactory $collectionFactory
* @param int $batchSize
*/
public function __construct(
- \Magento\Customer\Model\ResourceModel\Customer\Indexer\CollectionFactory $collectionFactory,
+ CollectionFactory $collectionFactory,
$batchSize = 10000
) {
$this->customerCollection = $collectionFactory->create();
@@ -37,7 +38,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getMainTable()
{
@@ -45,7 +46,7 @@ public function getMainTable()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIdFieldName()
{
@@ -53,7 +54,7 @@ public function getIdFieldName()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function addFieldToSelect($fieldName, $alias = null)
{
@@ -62,7 +63,7 @@ public function addFieldToSelect($fieldName, $alias = null)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getSelect()
{
@@ -70,7 +71,7 @@ public function getSelect()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function addFieldToFilter($attribute, $condition = null)
{
@@ -79,7 +80,7 @@ public function addFieldToFilter($attribute, $condition = null)
}
/**
- * @return int
+ * @inheritdoc
*/
public function count()
{
@@ -105,4 +106,28 @@ public function getIterator()
$pageNumber++;
} while ($pageNumber <= $lastPage);
}
+
+ /**
+ * Joins Attribute
+ *
+ * @param string $alias alias for the joined attribute
+ * @param string|\Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute
+ * @param string $bind attribute of the main entity to link with joined $filter
+ * @param string|null $filter primary key for the joined entity (entity_id default)
+ * @param string $joinType inner|left
+ * @param int|null $storeId
+ * @return void
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @see Collection::joinAttribute()
+ */
+ public function joinAttribute(
+ string $alias,
+ $attribute,
+ string $bind,
+ ?string $filter = null,
+ string $joinType = 'inner',
+ ?int $storeId = null
+ ): void {
+ $this->customerCollection->joinAttribute($alias, $attribute, $bind, $filter, $joinType, $storeId);
+ }
}
diff --git a/app/code/Magento/Customer/Model/Metadata/CustomerMetadata.php b/app/code/Magento/Customer/Model/Metadata/CustomerMetadata.php
index 7ed806e657e82..38f3fbcbdbded 100644
--- a/app/code/Magento/Customer/Model/Metadata/CustomerMetadata.php
+++ b/app/code/Magento/Customer/Model/Metadata/CustomerMetadata.php
@@ -33,20 +33,30 @@ class CustomerMetadata implements CustomerMetadataInterface
*/
private $attributeMetadataDataProvider;
+ /**
+ * List of system attributes which should be available to the clients.
+ *
+ * @var string[]
+ */
+ private $systemAttributes;
+
/**
* @param AttributeMetadataConverter $attributeMetadataConverter
* @param AttributeMetadataDataProvider $attributeMetadataDataProvider
+ * @param string[] $systemAttributes
*/
public function __construct(
AttributeMetadataConverter $attributeMetadataConverter,
- AttributeMetadataDataProvider $attributeMetadataDataProvider
+ AttributeMetadataDataProvider $attributeMetadataDataProvider,
+ array $systemAttributes = []
) {
$this->attributeMetadataConverter = $attributeMetadataConverter;
$this->attributeMetadataDataProvider = $attributeMetadataDataProvider;
+ $this->systemAttributes = $systemAttributes;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAttributes($formCode)
{
@@ -67,7 +77,7 @@ public function getAttributes($formCode)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAttributeMetadata($attributeCode)
{
@@ -92,7 +102,7 @@ public function getAttributeMetadata($attributeCode)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAllAttributesMetadata()
{
@@ -116,7 +126,7 @@ public function getAllAttributesMetadata()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getCustomAttributesMetadata($dataObjectClassName = self::DATA_INTERFACE_NAME)
{
@@ -134,9 +144,10 @@ public function getCustomAttributesMetadata($dataObjectClassName = self::DATA_IN
$isDataObjectMethod = isset($this->customerDataObjectMethods['get' . $camelCaseKey])
|| isset($this->customerDataObjectMethods['is' . $camelCaseKey]);
- /** Even though disable_auto_group_change is system attribute, it should be available to the clients */
if (!$isDataObjectMethod
- && (!$attributeMetadata->isSystem() || $attributeCode == 'disable_auto_group_change')
+ && (!$attributeMetadata->isSystem()
+ || in_array($attributeCode, $this->systemAttributes)
+ )
) {
$customAttributes[] = $attributeMetadata;
}
diff --git a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
index 43ae2db0c2163..a053eee5cd09b 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
@@ -8,69 +8,80 @@
use Magento\Customer\Api\CustomerMetadataInterface;
use Magento\Customer\Api\Data\CustomerInterface;
-use Magento\Customer\Model\Delegation\Data\NewOperation;
+use Magento\Customer\Api\Data\CustomerSearchResultsInterfaceFactory;
+use Magento\Framework\Api\ExtensibleDataObjectConverter;
+use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
+use Magento\Customer\Model\CustomerFactory;
+use Magento\Customer\Model\CustomerRegistry;
+use Magento\Customer\Model\Data\CustomerSecureFactory;
use Magento\Customer\Model\Customer\NotificationStorage;
+use Magento\Customer\Model\Delegation\Data\NewOperation;
+use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\Api\DataObjectHelper;
use Magento\Framework\Api\ImageProcessorInterface;
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
use Magento\Framework\Api\SearchCriteriaInterface;
+use Magento\Framework\Api\Search\FilterGroup;
+use Magento\Framework\Event\ManagerInterface;
use Magento\Customer\Model\Delegation\Storage as DelegatedStorage;
use Magento\Framework\App\ObjectManager;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Customer repository.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @SuppressWarnings(PHPMD.TooManyFields)
*/
-class CustomerRepository implements \Magento\Customer\Api\CustomerRepositoryInterface
+class CustomerRepository implements CustomerRepositoryInterface
{
/**
- * @var \Magento\Customer\Model\CustomerFactory
+ * @var CustomerFactory
*/
protected $customerFactory;
/**
- * @var \Magento\Customer\Model\Data\CustomerSecureFactory
+ * @var CustomerSecureFactory
*/
protected $customerSecureFactory;
/**
- * @var \Magento\Customer\Model\CustomerRegistry
+ * @var CustomerRegistry
*/
protected $customerRegistry;
/**
- * @var \Magento\Customer\Model\ResourceModel\AddressRepository
+ * @var AddressRepository
*/
protected $addressRepository;
/**
- * @var \Magento\Customer\Model\ResourceModel\Customer
+ * @var Customer
*/
protected $customerResourceModel;
/**
- * @var \Magento\Customer\Api\CustomerMetadataInterface
+ * @var CustomerMetadataInterface
*/
protected $customerMetadata;
/**
- * @var \Magento\Customer\Api\Data\CustomerSearchResultsInterfaceFactory
+ * @var CustomerSearchResultsInterfaceFactory
*/
protected $searchResultsFactory;
/**
- * @var \Magento\Framework\Event\ManagerInterface
+ * @var ManagerInterface
*/
protected $eventManager;
/**
- * @var \Magento\Store\Model\StoreManagerInterface
+ * @var StoreManagerInterface
*/
protected $storeManager;
/**
- * @var \Magento\Framework\Api\ExtensibleDataObjectConverter
+ * @var ExtensibleDataObjectConverter
*/
protected $extensibleDataObjectConverter;
@@ -85,7 +96,7 @@ class CustomerRepository implements \Magento\Customer\Api\CustomerRepositoryInte
protected $imageProcessor;
/**
- * @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface
+ * @var JoinProcessorInterface
*/
protected $extensionAttributesJoinProcessor;
@@ -105,38 +116,38 @@ class CustomerRepository implements \Magento\Customer\Api\CustomerRepositoryInte
private $delegatedStorage;
/**
- * @param \Magento\Customer\Model\CustomerFactory $customerFactory
- * @param \Magento\Customer\Model\Data\CustomerSecureFactory $customerSecureFactory
- * @param \Magento\Customer\Model\CustomerRegistry $customerRegistry
- * @param \Magento\Customer\Model\ResourceModel\AddressRepository $addressRepository
- * @param \Magento\Customer\Model\ResourceModel\Customer $customerResourceModel
- * @param \Magento\Customer\Api\CustomerMetadataInterface $customerMetadata
- * @param \Magento\Customer\Api\Data\CustomerSearchResultsInterfaceFactory $searchResultsFactory
- * @param \Magento\Framework\Event\ManagerInterface $eventManager
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter
+ * @param CustomerFactory $customerFactory
+ * @param CustomerSecureFactory $customerSecureFactory
+ * @param CustomerRegistry $customerRegistry
+ * @param AddressRepository $addressRepository
+ * @param Customer $customerResourceModel
+ * @param CustomerMetadataInterface $customerMetadata
+ * @param CustomerSearchResultsInterfaceFactory $searchResultsFactory
+ * @param ManagerInterface $eventManager
+ * @param StoreManagerInterface $storeManager
+ * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter
* @param DataObjectHelper $dataObjectHelper
* @param ImageProcessorInterface $imageProcessor
- * @param \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $extensionAttributesJoinProcessor
+ * @param JoinProcessorInterface $extensionAttributesJoinProcessor
* @param CollectionProcessorInterface $collectionProcessor
* @param NotificationStorage $notificationStorage
* @param DelegatedStorage|null $delegatedStorage
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
- \Magento\Customer\Model\CustomerFactory $customerFactory,
- \Magento\Customer\Model\Data\CustomerSecureFactory $customerSecureFactory,
- \Magento\Customer\Model\CustomerRegistry $customerRegistry,
- \Magento\Customer\Model\ResourceModel\AddressRepository $addressRepository,
- \Magento\Customer\Model\ResourceModel\Customer $customerResourceModel,
- \Magento\Customer\Api\CustomerMetadataInterface $customerMetadata,
- \Magento\Customer\Api\Data\CustomerSearchResultsInterfaceFactory $searchResultsFactory,
- \Magento\Framework\Event\ManagerInterface $eventManager,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter,
+ CustomerFactory $customerFactory,
+ CustomerSecureFactory $customerSecureFactory,
+ CustomerRegistry $customerRegistry,
+ AddressRepository $addressRepository,
+ Customer $customerResourceModel,
+ CustomerMetadataInterface $customerMetadata,
+ CustomerSearchResultsInterfaceFactory $searchResultsFactory,
+ ManagerInterface $eventManager,
+ StoreManagerInterface $storeManager,
+ ExtensibleDataObjectConverter $extensibleDataObjectConverter,
DataObjectHelper $dataObjectHelper,
ImageProcessorInterface $imageProcessor,
- \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $extensionAttributesJoinProcessor,
+ JoinProcessorInterface $extensionAttributesJoinProcessor,
CollectionProcessorInterface $collectionProcessor,
NotificationStorage $notificationStorage,
DelegatedStorage $delegatedStorage = null
@@ -156,8 +167,7 @@ public function __construct(
$this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor;
$this->collectionProcessor = $collectionProcessor;
$this->notificationStorage = $notificationStorage;
- $this->delegatedStorage = $delegatedStorage
- ?? ObjectManager::getInstance()->get(DelegatedStorage::class);
+ $this->delegatedStorage = $delegatedStorage ?? ObjectManager::getInstance()->get(DelegatedStorage::class);
}
/**
@@ -200,13 +210,13 @@ public function save(CustomerInterface $customer, $passwordHash = null)
$customerModel->setRpToken(null);
$customerModel->setRpTokenCreatedAt(null);
}
- if (!array_key_exists('default_billing', $customerArr)
+ if (!array_key_exists('addresses', $customerArr)
&& null !== $prevCustomerDataArr
&& array_key_exists('default_billing', $prevCustomerDataArr)
) {
$customerModel->setDefaultBilling($prevCustomerDataArr['default_billing']);
}
- if (!array_key_exists('default_shipping', $customerArr)
+ if (!array_key_exists('addresses', $customerArr)
&& null !== $prevCustomerDataArr
&& array_key_exists('default_shipping', $prevCustomerDataArr)
) {
@@ -371,15 +381,12 @@ public function deleteById($customerId)
* Helper function that adds a FilterGroup to the collection.
*
* @deprecated 100.2.0
- * @param \Magento\Framework\Api\Search\FilterGroup $filterGroup
- * @param \Magento\Customer\Model\ResourceModel\Customer\Collection $collection
+ * @param FilterGroup $filterGroup
+ * @param Collection $collection
* @return void
- * @throws \Magento\Framework\Exception\InputException
*/
- protected function addFilterGroupToCollection(
- \Magento\Framework\Api\Search\FilterGroup $filterGroup,
- \Magento\Customer\Model\ResourceModel\Customer\Collection $collection
- ) {
+ protected function addFilterGroupToCollection(FilterGroup $filterGroup, Collection $collection)
+ {
$fields = [];
foreach ($filterGroup->getFilters() as $filter) {
$condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq';
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml
new file mode 100644
index 0000000000000..8a3ab7068696c
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml
new file mode 100644
index 0000000000000..5497b083ab5ad
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLogoutActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
index 033add209a4c2..2bbe7930f6dbf 100755
--- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
@@ -32,6 +32,7 @@
vatData
true
true
+
66
John
diff --git a/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerConfigPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerConfigPage.xml
new file mode 100644
index 0000000000000..282f9bb6fdeb5
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerConfigPage.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAddressesPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAddressesPage.xml
new file mode 100644
index 0000000000000..b9bede5133060
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAddressesPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerLogoutPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerLogoutPage.xml
new file mode 100644
index 0000000000000..b3cea8f2c2939
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerLogoutPage.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml
new file mode 100644
index 0000000000000..9e104eb52cf90
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml
new file mode 100644
index 0000000000000..8a6a98ff45a6a
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml
index 81612d1c64334..f831aabddd4ee 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml
@@ -14,5 +14,7 @@
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml
index 06b82db767ab5..a0c83f5bc491b 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml
@@ -11,5 +11,6 @@
diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php
index f4bf184f9ebf2..5a7cf42be2c7e 100644
--- a/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php
@@ -83,13 +83,13 @@ protected function setUp()
}
/**
- * @param $sectionNames
- * @param $updateSectionID
- * @param $sectionNamesAsArray
- * @param $updateIds
+ * @param string $sectionNames
+ * @param bool $forceNewSectionTimestamp
+ * @param string[] $sectionNamesAsArray
+ * @param bool $forceNewTimestamp
* @dataProvider executeDataProvider
*/
- public function testExecute($sectionNames, $updateSectionID, $sectionNamesAsArray, $updateIds)
+ public function testExecute($sectionNames, $forceNewSectionTimestamp, $sectionNamesAsArray, $forceNewTimestamp)
{
$this->resultJsonFactoryMock->expects($this->once())
->method('create')
@@ -103,12 +103,12 @@ public function testExecute($sectionNames, $updateSectionID, $sectionNamesAsArra
$this->httpRequestMock->expects($this->exactly(2))
->method('getParam')
- ->withConsecutive(['sections'], ['update_section_id'])
- ->willReturnOnConsecutiveCalls($sectionNames, $updateSectionID);
+ ->withConsecutive(['sections'], ['force_new_section_timestamp'])
+ ->willReturnOnConsecutiveCalls($sectionNames, $forceNewSectionTimestamp);
$this->sectionPoolMock->expects($this->once())
->method('getSectionsData')
- ->with($sectionNamesAsArray, $updateIds)
+ ->with($sectionNamesAsArray, $forceNewTimestamp)
->willReturn([
'message' => 'some message',
'someKey' => 'someValue'
@@ -133,15 +133,15 @@ public function executeDataProvider()
return [
[
'sectionNames' => 'sectionName1,sectionName2,sectionName3',
- 'updateSectionID' => 'updateSectionID',
+ 'forceNewSectionTimestamp' => 'forceNewSectionTimestamp',
'sectionNamesAsArray' => ['sectionName1', 'sectionName2', 'sectionName3'],
- 'updateIds' => true
+ 'forceNewTimestamp' => true
],
[
'sectionNames' => null,
- 'updateSectionID' => null,
+ 'forceNewSectionTimestamp' => null,
'sectionNamesAsArray' => null,
- 'updateIds' => false
+ 'forceNewTimestamp' => false
],
];
}
diff --git a/app/code/Magento/Customer/Test/Unit/CustomerData/SectionPoolTest.php b/app/code/Magento/Customer/Test/Unit/CustomerData/SectionPoolTest.php
index 98fee70e335f7..2b67df1aee292 100644
--- a/app/code/Magento/Customer/Test/Unit/CustomerData/SectionPoolTest.php
+++ b/app/code/Magento/Customer/Test/Unit/CustomerData/SectionPoolTest.php
@@ -63,7 +63,7 @@ public function testGetSectionsDataAllSections()
$this->identifierMock->expects($this->once())
->method('markSections')
- //check also default value for $updateIds = false
+ //check also default value for $forceTimestamp = false
->with($allSectionsData, $sectionNames, false)
->willReturn($identifierResult);
$modelResult = $this->model->getSectionsData($sectionNames);
diff --git a/app/code/Magento/Customer/etc/di.xml b/app/code/Magento/Customer/etc/di.xml
index 6e8c3dc68ed28..209f4794c6fcf 100644
--- a/app/code/Magento/Customer/etc/di.xml
+++ b/app/code/Magento/Customer/etc/di.xml
@@ -127,6 +127,13 @@
Magento\Customer\Api\GroupManagementInterface\Proxy
+
+
+
+ - disable_auto_group_change
+
+
+
diff --git a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js
index cfa06c6e12003..39e3f8d95ee3b 100644
--- a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js
+++ b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js
@@ -75,17 +75,17 @@ define([
/**
* @param {Object} sectionNames
- * @param {Number} updateSectionId
+ * @param {Boolean} forceNewSectionTimestamp
* @return {*}
*/
- getFromServer: function (sectionNames, updateSectionId) {
+ getFromServer: function (sectionNames, forceNewSectionTimestamp) {
var parameters;
sectionNames = sectionConfig.filterClientSideSections(sectionNames);
parameters = _.isArray(sectionNames) ? {
sections: sectionNames.join(',')
} : [];
- parameters['update_section_id'] = updateSectionId;
+ parameters['force_new_section_timestamp'] = forceNewSectionTimestamp;
return $.getJSON(options.sectionLoadUrl, parameters).fail(function (jqXHR) {
throw new Error(jqXHR);
@@ -326,11 +326,11 @@ define([
/**
* @param {Array} sectionNames
- * @param {Number} updateSectionId
+ * @param {Boolean} forceNewSectionTimestamp
* @return {*}
*/
- reload: function (sectionNames, updateSectionId) {
- return dataProvider.getFromServer(sectionNames, updateSectionId).done(function (sections) {
+ reload: function (sectionNames, forceNewSectionTimestamp) {
+ return dataProvider.getFromServer(sectionNames, forceNewSectionTimestamp).done(function (sections) {
$(document).trigger('customer-data-reload', [sectionNames]);
buffer.update(sections);
});
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php b/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php
index d235d51b5e52d..36365f1910f27 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php
@@ -73,7 +73,7 @@ public function execute(int $customerId, array $data): void
if (isset($data['email']) && $customer->getEmail() !== $data['email']) {
if (!isset($data['password']) || empty($data['password'])) {
- throw new GraphQlInputException(__('For changing "email" you should provide current "password".'));
+ throw new GraphQlInputException(__('Provide the current "password" to change "email".'));
}
$this->checkCustomerPassword->execute($data['password'], $customerId);
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php
index 98b0975b37169..78fa852a7ac59 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php
@@ -17,7 +17,7 @@
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
/**
- * @inheritdoc
+ * Change customer password resolver
*/
class ChangePassword implements ResolverInterface
{
@@ -70,11 +70,11 @@ public function resolve(
array $args = null
) {
if (!isset($args['currentPassword'])) {
- throw new GraphQlInputException(__('"currentPassword" value should be specified'));
+ throw new GraphQlInputException(__('Specify the "currentPassword" value.'));
}
if (!isset($args['newPassword'])) {
- throw new GraphQlInputException(__('"newPassword" value should be specified'));
+ throw new GraphQlInputException(__('Specify the "newPassword" value.'));
}
$currentUserId = $context->getUserId();
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php
index 9719d048f606b..1bd77fe1cde8f 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php
@@ -45,11 +45,11 @@ public function resolve(
array $args = null
) {
if (!isset($args['email'])) {
- throw new GraphQlInputException(__('"email" value should be specified'));
+ throw new GraphQlInputException(__('Specify the "email" value.'));
}
if (!isset($args['password'])) {
- throw new GraphQlInputException(__('"password" value should be specified'));
+ throw new GraphQlInputException(__('Specify the "password" value.'));
}
try {
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php
index d3b16c05a6492..3301162dc0088 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php
@@ -55,6 +55,6 @@ public function resolve(
$this->checkCustomerAccount->execute($currentUserId, $currentUserType);
- return $this->customerTokenService->revokeCustomerAccessToken((int)$currentUserId);
+ return ['result' => $this->customerTokenService->revokeCustomerAccessToken((int)$currentUserId)];
}
}
diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls
index b8411f00c5cb1..ca4ac6cc245a7 100644
--- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls
@@ -6,10 +6,10 @@ type Query {
}
type Mutation {
- generateCustomerToken(email: String!, password: String!): CustomerToken @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\GenerateCustomerToken") @doc(description:"Retrieve Customer token")
- changeCustomerPassword(currentPassword: String!, newPassword: String!): Customer @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ChangePassword") @doc(description:"Changes password for logged in customer")
- updateCustomer (input: UpdateCustomerInput): UpdateCustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Update customer personal information")
- revokeCustomerToken: Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RevokeCustomerToken") @doc(description:"Revoke Customer token")
+ generateCustomerToken(email: String!, password: String!): CustomerToken @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\GenerateCustomerToken") @doc(description:"Retrieve the customer token")
+ changeCustomerPassword(currentPassword: String!, newPassword: String!): Customer @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ChangePassword") @doc(description:"Changes the password for the logged-in customer")
+ updateCustomer (input: UpdateCustomerInput!): UpdateCustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Update the customer's personal information")
+ revokeCustomerToken: RevokeCustomerTokenOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RevokeCustomerToken") @doc(description:"Revoke the customer token")
}
type CustomerToken {
@@ -28,6 +28,10 @@ type UpdateCustomerOutput {
customer: Customer!
}
+type RevokeCustomerTokenOutput {
+ result: Boolean!
+}
+
type Customer @doc(description: "Customer defines the customer name and address and other details") {
created_at: String @doc(description: "Timestamp indicating when the account was created")
group_id: Int @doc(description: "The group assigned to the user. Default values are 0 (Not logged in), 1 (General), 2 (Wholesale), and 3 (Retailer)")
diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Downloadable.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Downloadable.php
index f56b219f72db2..a283891afc406 100644
--- a/app/code/Magento/Downloadable/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Downloadable.php
+++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Downloadable.php
@@ -11,6 +11,9 @@
use Magento\Downloadable\Api\Data\SampleInterfaceFactory;
use Magento\Downloadable\Api\Data\LinkInterfaceFactory;
+/**
+ * Class for initialization downloadable info from request.
+ */
class Downloadable
{
/**
@@ -92,6 +95,8 @@ public function afterInitialize(
}
}
$extension->setDownloadableProductLinks($links);
+ } else {
+ $extension->setDownloadableProductLinks([]);
}
if (isset($downloadable['sample']) && is_array($downloadable['sample'])) {
$samples = [];
@@ -107,6 +112,8 @@ public function afterInitialize(
}
}
$extension->setDownloadableProductSamples($samples);
+ } else {
+ $extension->setDownloadableProductSamples([]);
}
$product->setExtensionAttributes($extension);
if ($product->getLinksPurchasedSeparately()) {
diff --git a/app/code/Magento/DownloadableGraphQl/Model/Resolver/Product/DownloadableOptions.php b/app/code/Magento/DownloadableGraphQl/Model/Resolver/Product/DownloadableOptions.php
index 5141361fecc0e..a1e25663a9c3d 100644
--- a/app/code/Magento/DownloadableGraphQl/Model/Resolver/Product/DownloadableOptions.php
+++ b/app/code/Magento/DownloadableGraphQl/Model/Resolver/Product/DownloadableOptions.php
@@ -7,7 +7,8 @@
namespace Magento\DownloadableGraphQl\Model\Resolver\Product;
-use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Catalog\Model\Product;
use Magento\Downloadable\Helper\Data as DownloadableHelper;
@@ -20,9 +21,9 @@
use Magento\Framework\GraphQl\Query\ResolverInterface;
/**
- * Format for downloadable product types
+ * @inheritdoc
*
- * {@inheritdoc}
+ * Format for downloadable product types
*/
class DownloadableOptions implements ResolverInterface
{
@@ -65,9 +66,17 @@ public function __construct(
}
/**
+ * @inheritdoc
+ *
* Add downloadable options to configurable types
*
- * {@inheritdoc}
+ * @param \Magento\Framework\GraphQl\Config\Element\Field $field
+ * @param ContextInterface $context
+ * @param ResolveInfo $info
+ * @param array|null $value
+ * @param array|null $args
+ * @throws \Exception
+ * @return null|array
*/
public function resolve(
Field $field,
@@ -77,7 +86,7 @@ public function resolve(
array $args = null
) {
if (!isset($value['model'])) {
- throw new GraphQlInputException(__('"model" value should be specified'));
+ throw new LocalizedException(__('"model" value should be specified'));
}
/** @var Product $product */
diff --git a/app/code/Magento/EavGraphQl/Model/Resolver/AttributeOptions.php b/app/code/Magento/EavGraphQl/Model/Resolver/AttributeOptions.php
index 6ccd610bead0d..e4c27adc60247 100644
--- a/app/code/Magento/EavGraphQl/Model/Resolver/AttributeOptions.php
+++ b/app/code/Magento/EavGraphQl/Model/Resolver/AttributeOptions.php
@@ -9,6 +9,7 @@
use Magento\EavGraphQl\Model\Resolver\DataProvider\AttributeOptions as AttributeOptionsDataProvider;
use Magento\Framework\Exception\InputException;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\StateException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
@@ -46,7 +47,7 @@ public function __construct(
}
/**
- * @inheritDoc
+ * @inheritdoc
*/
public function resolve(
Field $field,
@@ -66,34 +67,40 @@ public function resolve(
}
/**
+ * Get entity type
+ *
* @param array $value
* @return int
- * @throws GraphQlInputException
+ * @throws LocalizedException
*/
private function getEntityType(array $value): int
{
if (!isset($value['entity_type'])) {
- throw new GraphQlInputException(__('"Entity type should be specified'));
+ throw new LocalizedException(__('"Entity type should be specified'));
}
return (int)$value['entity_type'];
}
/**
+ * Get attribute code
+ *
* @param array $value
* @return string
- * @throws GraphQlInputException
+ * @throws LocalizedException
*/
private function getAttributeCode(array $value): string
{
if (!isset($value['attribute_code'])) {
- throw new GraphQlInputException(__('"Attribute code should be specified'));
+ throw new LocalizedException(__('"Attribute code should be specified'));
}
return $value['attribute_code'];
}
/**
+ * Get attribute options data
+ *
* @param int $entityType
* @param string $attributeCode
* @return array
diff --git a/app/code/Magento/EavGraphQl/Model/Resolver/Query/Type.php b/app/code/Magento/EavGraphQl/Model/Resolver/Query/Type.php
index cbea3a86bbddd..ef21a26f1f62e 100644
--- a/app/code/Magento/EavGraphQl/Model/Resolver/Query/Type.php
+++ b/app/code/Magento/EavGraphQl/Model/Resolver/Query/Type.php
@@ -9,7 +9,6 @@
use Magento\Framework\Webapi\CustomAttributeTypeLocatorInterface;
use Magento\Framework\Reflection\TypeProcessor;
-use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
/**
@@ -37,7 +36,7 @@ class Type
/**
* @param CustomAttributeTypeLocatorInterface $typeLocator
* @param TypeProcessor $typeProcessor
- * @param $customTypes
+ * @param array $customTypes
*/
public function __construct(
CustomAttributeTypeLocatorInterface $typeLocator,
@@ -71,12 +70,7 @@ public function getType(string $attributeCode, string $entityType) : string
try {
$type = $this->typeProcessor->translateTypeName($type);
} catch (\InvalidArgumentException $exception) {
- throw new GraphQlInputException(
- __('Type %1 has no internal representation declared.', [$type]),
- null,
- 0,
- false
- );
+ throw new GraphQlInputException(__('Cannot resolve EAV type'));
}
} else {
$type = $type === 'double' ? 'float' : $type;
diff --git a/app/code/Magento/Email/Model/Transport.php b/app/code/Magento/Email/Model/Transport.php
index 5a4d64e6d6c0e..28d2d171eecf4 100644
--- a/app/code/Magento/Email/Model/Transport.php
+++ b/app/code/Magento/Email/Model/Transport.php
@@ -31,6 +31,23 @@ class Transport implements TransportInterface
*/
const XML_PATH_SENDING_RETURN_PATH_EMAIL = 'system/smtp/return_path_email';
+ /**
+ * Whether return path should be set or no.
+ *
+ * Possible values are:
+ * 0 - no
+ * 1 - yes (set value as FROM address)
+ * 2 - use custom value
+ *
+ * @var int
+ */
+ private $isSetReturnPath;
+
+ /**
+ * @var string|null
+ */
+ private $returnPathValue;
+
/**
* @var Sendmail
*/
@@ -51,25 +68,15 @@ public function __construct(
ScopeConfigInterface $scopeConfig,
$parameters = null
) {
- /* configuration of whether return path should be set or no. Possible values are:
- * 0 - no
- * 1 - yes (set value as FROM address)
- * 2 - use custom value
- * @see Magento\Config\Model\Config\Source\Yesnocustom
- */
- $isSetReturnPath = $scopeConfig->getValue(
+ $this->isSetReturnPath = (int) $scopeConfig->getValue(
self::XML_PATH_SENDING_SET_RETURN_PATH,
ScopeInterface::SCOPE_STORE
);
- $returnPathValue = $scopeConfig->getValue(
+ $this->returnPathValue = $scopeConfig->getValue(
self::XML_PATH_SENDING_RETURN_PATH_EMAIL,
ScopeInterface::SCOPE_STORE
);
- if ($isSetReturnPath == '2' && $returnPathValue !== null) {
- $parameters .= ' -f' . \escapeshellarg($returnPathValue);
- }
-
$this->zendTransport = new Sendmail($parameters);
$this->message = $message;
}
@@ -80,9 +87,16 @@ public function __construct(
public function sendMessage()
{
try {
- $this->zendTransport->send(
- Message::fromString($this->message->getRawMessage())
- );
+ $zendMessage = Message::fromString($this->message->getRawMessage());
+ if (2 === $this->isSetReturnPath && $this->returnPathValue) {
+ $zendMessage->setSender($this->returnPathValue);
+ } elseif (1 === $this->isSetReturnPath && $zendMessage->getFrom()->count()) {
+ $fromAddressList = $zendMessage->getFrom();
+ $fromAddressList->rewind();
+ $zendMessage->setSender($fromAddressList->current()->getEmail());
+ }
+
+ $this->zendTransport->send($zendMessage);
} catch (\Exception $e) {
throw new MailException(new Phrase($e->getMessage()), $e);
}
diff --git a/app/code/Magento/GraphQl/Model/EntityAttributeList.php b/app/code/Magento/GraphQl/Model/EntityAttributeList.php
index 6b8a4f477069e..3802b74f3ec13 100644
--- a/app/code/Magento/GraphQl/Model/EntityAttributeList.php
+++ b/app/code/Magento/GraphQl/Model/EntityAttributeList.php
@@ -14,7 +14,7 @@
use Magento\Framework\Api\MetadataServiceInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Exception\NoSuchEntityException;
-use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
/**
* Iterate through all attribute sets to retrieve attributes for any given entity type
@@ -69,7 +69,7 @@ public function __construct(
* @param string $entityCode
* @param MetadataServiceInterface $metadataService
* @return boolean[]
- * @throws GraphQlInputException
+ * @throws GraphQlNoSuchEntityException
*/
public function getDefaultEntityAttributes(
string $entityCode,
@@ -93,7 +93,7 @@ public function getDefaultEntityAttributes(
$this->attributeManagement->getAttributes($entityCode, $attributeSet->getAttributeSetId())
);
} catch (NoSuchEntityException $exception) {
- throw new GraphQlInputException(__('Entity code %1 does not exist.', [$entityCode]));
+ throw new GraphQlNoSuchEntityException(__('Entity code %1 does not exist.', [$entityCode]));
}
}
$attributeCodes = [];
diff --git a/app/code/Magento/GraphQl/etc/di.xml b/app/code/Magento/GraphQl/etc/di.xml
index 105fa8bf166b2..b2083ea758e56 100644
--- a/app/code/Magento/GraphQl/etc/di.xml
+++ b/app/code/Magento/GraphQl/etc/di.xml
@@ -97,4 +97,10 @@
+
+
+ 20
+ 300
+
+
diff --git a/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php b/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php
index 673450838fb94..f67c9c57ee034 100644
--- a/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php
+++ b/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php
@@ -347,7 +347,7 @@ protected function getProductInfo(\Magento\Framework\DataObject $buyRequest, $pr
if ($isStrictProcessMode && !$subProduct->getQty()) {
return __('Please specify the quantity of product(s).')->render();
}
- $productsInfo[$subProduct->getId()] = (int)$subProduct->getQty();
+ $productsInfo[$subProduct->getId()] = $subProduct->isSalable() ? (float)$subProduct->getQty() : 0;
}
}
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminProductFormGroupedProductsSection.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminProductFormGroupedProductsSection.xml
index 64dcd9566d890..547c856d144c8 100644
--- a/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminProductFormGroupedProductsSection.xml
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminProductFormGroupedProductsSection.xml
@@ -11,5 +11,9 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminSortingAssociatedProductsTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminSortingAssociatedProductsTest.xml
new file mode 100644
index 0000000000000..ad5fbbb30edeb
--- /dev/null
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminSortingAssociatedProductsTest.xml
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $grabNameProductPosition0
+ $grabNameProductPosition21
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $grabNameProductPosition2
+ $grabNameProductPosition0
+
+
+ $grabNameProductPositionFirst
+ $grabNameProductPositionZero
+
+
+
diff --git a/app/code/Magento/GroupedProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GroupedTest.php b/app/code/Magento/GroupedProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GroupedTest.php
index 6ef87117deae2..327b47d4a75d8 100644
--- a/app/code/Magento/GroupedProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GroupedTest.php
+++ b/app/code/Magento/GroupedProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GroupedTest.php
@@ -34,6 +34,7 @@ class GroupedTest extends AbstractModifierTest
const LINKED_PRODUCT_NAME = 'linked';
const LINKED_PRODUCT_QTY = '0';
const LINKED_PRODUCT_POSITION = 1;
+ const LINKED_PRODUCT_POSITION_CALCULATED = 1;
const LINKED_PRODUCT_PRICE = '1';
/**
@@ -212,6 +213,7 @@ public function testModifyData()
'price' => null,
'qty' => self::LINKED_PRODUCT_QTY,
'position' => self::LINKED_PRODUCT_POSITION,
+ 'positionCalculated' => self::LINKED_PRODUCT_POSITION_CALCULATED,
'thumbnail' => null,
'type_id' => null,
'status' => null,
diff --git a/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php b/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php
index 2d1a1d19757e2..57d9bc78aaf28 100644
--- a/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php
+++ b/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php
@@ -133,7 +133,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function modifyData(array $data)
{
@@ -143,12 +143,17 @@ public function modifyData(array $data)
if ($modelId) {
$storeId = $this->locator->getStore()->getId();
$data[$product->getId()]['links'][self::LINK_TYPE] = [];
- foreach ($this->productLinkRepository->getList($product) as $linkItem) {
+ $linkedItems = $this->productLinkRepository->getList($product);
+ usort($linkedItems, function ($a, $b) {
+ return $a->getPosition() <=> $b->getPosition();
+ });
+ foreach ($linkedItems as $index => $linkItem) {
if ($linkItem->getLinkType() !== self::LINK_TYPE) {
continue;
}
/** @var \Magento\Catalog\Api\Data\ProductInterface $linkedProduct */
$linkedProduct = $this->productRepository->get($linkItem->getLinkedProductSku(), false, $storeId);
+ $linkItem->setPosition($index);
$data[$modelId]['links'][self::LINK_TYPE][] = $this->fillData($linkedProduct, $linkItem);
}
$data[$modelId][self::DATA_SOURCE_DEFAULT]['current_store_id'] = $storeId;
@@ -175,6 +180,7 @@ protected function fillData(ProductInterface $linkedProduct, ProductLinkInterfac
'price' => $currency->toCurrency(sprintf("%f", $linkedProduct->getPrice())),
'qty' => $linkItem->getExtensionAttributes()->getQty(),
'position' => $linkItem->getPosition(),
+ 'positionCalculated' => $linkItem->getPosition(),
'thumbnail' => $this->imageHelper->init($linkedProduct, 'product_listing_thumbnail')->getUrl(),
'type_id' => $linkedProduct->getTypeId(),
'status' => $this->status->getOptionText($linkedProduct->getStatus()),
@@ -185,7 +191,7 @@ protected function fillData(ProductInterface $linkedProduct, ProductLinkInterfac
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function modifyMeta(array $meta)
{
@@ -454,7 +460,7 @@ protected function getGrid()
'label' => null,
'renderDefaultRecord' => false,
'template' => 'ui/dynamic-rows/templates/grid',
- 'component' => 'Magento_Ui/js/dynamic-rows/dynamic-rows-grid',
+ 'component' => 'Magento_GroupedProduct/js/grouped-product-grid',
'addButton' => false,
'itemTemplate' => 'record',
'dataScope' => 'data.links',
@@ -555,6 +561,22 @@ protected function fillMeta()
],
],
],
+ 'positionCalculated' => [
+ 'arguments' => [
+ 'data' => [
+ 'config' => [
+ 'label' => __('Position'),
+ 'dataType' => Form\Element\DataType\Number::NAME,
+ 'formElement' => Form\Element\Input::NAME,
+ 'componentType' => Form\Field::NAME,
+ 'elementTmpl' => 'Magento_GroupedProduct/components/position',
+ 'sortOrder' => 90,
+ 'fit' => true,
+ 'dataScope' => 'positionCalculated'
+ ],
+ ],
+ ],
+ ],
'actionDelete' => [
'arguments' => [
'data' => [
@@ -563,7 +585,7 @@ protected function fillMeta()
'componentType' => 'actionDelete',
'dataType' => Form\Element\DataType\Text::NAME,
'label' => __('Actions'),
- 'sortOrder' => 90,
+ 'sortOrder' => 100,
'fit' => true,
],
],
@@ -577,7 +599,7 @@ protected function fillMeta()
'formElement' => Form\Element\Input::NAME,
'componentType' => Form\Field::NAME,
'dataScope' => 'position',
- 'sortOrder' => 100,
+ 'sortOrder' => 110,
'visible' => false,
],
],
diff --git a/app/code/Magento/GroupedProduct/view/adminhtml/web/css/grouped-product.css b/app/code/Magento/GroupedProduct/view/adminhtml/web/css/grouped-product.css
index 3d723387d23b0..9142eaf90899e 100644
--- a/app/code/Magento/GroupedProduct/view/adminhtml/web/css/grouped-product.css
+++ b/app/code/Magento/GroupedProduct/view/adminhtml/web/css/grouped-product.css
@@ -66,3 +66,49 @@
overflow: hidden;
text-overflow: ellipsis;
}
+
+.position {
+ width: 100px;
+}
+
+.icon-rearrange-position > span {
+ border: 0;
+ clip: rect(0, 0, 0, 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+
+.icon-forward,
+.icon-backward {
+ -webkit-font-smoothing: antialiased;
+ font-family: 'Admin Icons';
+ font-size: 17px;
+ speak: none;
+}
+
+.position > * {
+ float: left;
+ margin: 3px;
+}
+
+.position-widget-input {
+ text-align: center;
+ width: 40px;
+}
+
+.icon-forward:before {
+ content: '\e618';
+}
+
+.icon-backward:before {
+ content: '\e619';
+}
+
+.icon-rearrange-position, .icon-rearrange-position:hover {
+ color: #d9d9d9;
+ text-decoration: none;
+}
diff --git a/app/code/Magento/GroupedProduct/view/adminhtml/web/js/grouped-product-grid.js b/app/code/Magento/GroupedProduct/view/adminhtml/web/js/grouped-product-grid.js
new file mode 100644
index 0000000000000..0ac3b58d6e3a7
--- /dev/null
+++ b/app/code/Magento/GroupedProduct/view/adminhtml/web/js/grouped-product-grid.js
@@ -0,0 +1,219 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+ 'underscore',
+ 'uiRegistry',
+ 'Magento_Ui/js/dynamic-rows/dynamic-rows-grid'
+], function (_, registry, dynamicRowsGrid) {
+ 'use strict';
+
+ return dynamicRowsGrid.extend({
+
+ /**
+ * Set max element position
+ *
+ * @param {Number} position - element position
+ * @param {Object} elem - instance
+ */
+ setMaxPosition: function (position, elem) {
+
+ if (position || position === 0) {
+ this.checkMaxPosition(position);
+ this.sort(position, elem);
+
+ if (~~position === this.maxPosition && ~~position > this.getDefaultPageBoundary() + 1) {
+ this.shiftNextPagesPositions(position);
+ }
+ } else {
+ this.maxPosition += 1;
+ }
+ },
+
+ /**
+ * Shift positions for next page elements
+ *
+ * @param {Number} position
+ */
+ shiftNextPagesPositions: function (position) {
+
+ var recordData = this.recordData(),
+ startIndex = ~~this.currentPage() * this.pageSize,
+ offset = position - startIndex + 1,
+ index = startIndex;
+
+ if (~~this.currentPage() === this.pages()) {
+ return false;
+ }
+
+ for (index; index < recordData.length; index++) {
+ recordData[index].position = index + offset;
+ }
+ this.recordData(recordData);
+ },
+
+ /**
+ * Update position for element after position from another page is entered
+ *
+ * @param {Object} data
+ * @param {Object} event
+ */
+ updateGridPosition: function (data, event) {
+ var inputValue = parseInt(event.target.value, 10),
+ recordData = this.recordData(),
+ record,
+ previousValue,
+ updatedRecord;
+
+ record = this.elems().find(function (obj) {
+ return obj.dataScope === data.parentScope;
+ });
+
+ previousValue = this.getCalculatedPosition(record);
+
+ if (isNaN(inputValue) || inputValue < 0 || inputValue === previousValue) {
+ return false;
+ }
+
+ this.elems([]);
+
+ updatedRecord = this.getUpdatedRecordIndex(recordData, record.data().id);
+
+ if (inputValue >= this.recordData().size() - 1) {
+ recordData[updatedRecord].position = this.getGlobalMaxPosition() + 1;
+ } else {
+ recordData.forEach(function (value, index) {
+ if (~~value.id === ~~record.data().id) {
+ recordData[index].position = inputValue;
+ } else if (inputValue > previousValue && index <= inputValue) {
+ recordData[index].position = index - 1;
+ } else if (inputValue < previousValue && index >= inputValue) {
+ recordData[index].position = index + 1;
+ }
+ });
+ }
+
+ this.reloadGridData(recordData);
+
+ },
+
+ /**
+ * Get updated record index
+ *
+ * @param {Array} recordData
+ * @param {Number} recordId
+ * @return {Number}
+ */
+ getUpdatedRecordIndex: function (recordData, recordId) {
+ return recordData.map(function (o) {
+ return ~~o.id;
+ }).indexOf(~~recordId);
+ },
+
+ /**
+ *
+ * @param {Array} recordData - to reprocess
+ */
+ reloadGridData: function (recordData) {
+ this.recordData(recordData.sort(function (a, b) {
+ return ~~a.position - ~~b.position;
+ }));
+ this._updateCollection();
+ this.reload();
+ },
+
+ /**
+ * Event handler for "Send to bottom" button
+ *
+ * @param {Object} positionObj
+ * @return {Boolean}
+ */
+ sendToBottom: function (positionObj) {
+
+ var objectToUpdate = this.getObjectToUpdate(positionObj),
+ recordData = this.recordData(),
+ updatedRecord;
+
+ if (~~this.currentPage() === this.pages) {
+ objectToUpdate.position = this.maxPosition;
+ } else {
+ this.elems([]);
+ updatedRecord = this.getUpdatedRecordIndex(recordData, objectToUpdate.data().id);
+ recordData[updatedRecord].position = this.getGlobalMaxPosition() + 1;
+ this.reloadGridData(recordData);
+ }
+
+ return false;
+ },
+
+ /**
+ * Event handler for "Send to top" button
+ *
+ * @param {Object} positionObj
+ * @return {Boolean}
+ */
+ sendToTop: function (positionObj) {
+ var objectToUpdate = this.getObjectToUpdate(positionObj),
+ recordData = this.recordData(),
+ updatedRecord;
+
+ //isFirst
+ if (~~this.currentPage() === 1) {
+ objectToUpdate.position = 0;
+ } else {
+ this.elems([]);
+ updatedRecord = this.getUpdatedRecordIndex(recordData, objectToUpdate.data().id);
+ recordData.forEach(function (value, index) {
+ recordData[index].position = index === updatedRecord ? 0 : value.position + 1;
+ });
+ this.reloadGridData(recordData);
+ }
+
+ return false;
+ },
+
+ /**
+ * Get element from grid for update
+ *
+ * @param {Object} object
+ * @return {*}
+ */
+ getObjectToUpdate: function (object) {
+ return this.elems().filter(function (item) {
+ return item.name === object.parentName;
+ })[0];
+ },
+
+ /**
+ * Value function for position input
+ *
+ * @param {Object} data
+ * @return {Number}
+ */
+ getCalculatedPosition: function (data) {
+ return (~~this.currentPage() - 1) * this.pageSize + this.elems().pluck('name').indexOf(data.name);
+ },
+
+ /**
+ * Return Page Boundary
+ *
+ * @return {Number}
+ */
+ getDefaultPageBoundary: function () {
+ return ~~this.currentPage() * this.pageSize - 1;
+ },
+
+ /**
+ * Returns position for last element to be moved after
+ *
+ * @return {Number}
+ */
+ getGlobalMaxPosition: function () {
+ return _.max(this.recordData().map(function (r) {
+ return ~~r.position;
+ }));
+ }
+ });
+});
diff --git a/app/code/Magento/GroupedProduct/view/adminhtml/web/template/components/position.html b/app/code/Magento/GroupedProduct/view/adminhtml/web/template/components/position.html
new file mode 100644
index 0000000000000..050fb3c2898ce
--- /dev/null
+++ b/app/code/Magento/GroupedProduct/view/adminhtml/web/template/components/position.html
@@ -0,0 +1,19 @@
+
diff --git a/app/code/Magento/GroupedProductGraphQl/Model/Resolver/GroupedItems.php b/app/code/Magento/GroupedProductGraphQl/Model/Resolver/GroupedItems.php
index fee4063affef1..d51b22ffe21df 100644
--- a/app/code/Magento/GroupedProductGraphQl/Model/Resolver/GroupedItems.php
+++ b/app/code/Magento/GroupedProductGraphQl/Model/Resolver/GroupedItems.php
@@ -7,7 +7,7 @@
namespace Magento\GroupedProductGraphQl\Model\Resolver;
-use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\Product;
use Magento\Framework\GraphQl\Config\Element\Field;
@@ -44,7 +44,7 @@ public function resolve(
array $args = null
) {
if (!isset($value['model'])) {
- throw new GraphQlInputException(__('"model" value should be specified'));
+ throw new LocalizedException(__('"model" value should be specified'));
}
$data = [];
diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php
index 39d0d5c7feaee..8fd73e466083a 100644
--- a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php
+++ b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php
@@ -212,7 +212,10 @@ protected function _prepareForm()
'label' => __('Select File to Import'),
'title' => __('Select File to Import'),
'required' => true,
- 'class' => 'input-file'
+ 'class' => 'input-file',
+ 'note' => __(
+ 'File must be saved in UTF-8 encoding for proper import'
+ ),
]
);
$fieldsets['upload']->addField(
diff --git a/app/code/Magento/Integration/Model/CustomerTokenService.php b/app/code/Magento/Integration/Model/CustomerTokenService.php
index 3c245804a9f6e..7c2c444a734eb 100644
--- a/app/code/Magento/Integration/Model/CustomerTokenService.php
+++ b/app/code/Magento/Integration/Model/CustomerTokenService.php
@@ -14,7 +14,11 @@
use Magento\Integration\Model\ResourceModel\Oauth\Token\CollectionFactory as TokenCollectionFactory;
use Magento\Integration\Model\Oauth\Token\RequestThrottler;
use Magento\Framework\Exception\AuthenticationException;
+use Magento\Framework\Event\ManagerInterface;
+/**
+ * @inheritdoc
+ */
class CustomerTokenService implements \Magento\Integration\Api\CustomerTokenServiceInterface
{
/**
@@ -24,6 +28,11 @@ class CustomerTokenService implements \Magento\Integration\Api\CustomerTokenServ
*/
private $tokenModelFactory;
+ /**
+ * @var Magento\Framework\Event\ManagerInterface
+ */
+ private $eventManager;
+
/**
* Customer Account Service
*
@@ -55,21 +64,25 @@ class CustomerTokenService implements \Magento\Integration\Api\CustomerTokenServ
* @param AccountManagementInterface $accountManagement
* @param TokenCollectionFactory $tokenModelCollectionFactory
* @param \Magento\Integration\Model\CredentialsValidator $validatorHelper
+ * @param \Magento\Framework\Event\ManagerInterface $eventManager
*/
public function __construct(
TokenModelFactory $tokenModelFactory,
AccountManagementInterface $accountManagement,
TokenCollectionFactory $tokenModelCollectionFactory,
- CredentialsValidator $validatorHelper
+ CredentialsValidator $validatorHelper,
+ ManagerInterface $eventManager = null
) {
$this->tokenModelFactory = $tokenModelFactory;
$this->accountManagement = $accountManagement;
$this->tokenModelCollectionFactory = $tokenModelCollectionFactory;
$this->validatorHelper = $validatorHelper;
+ $this->eventManager = $eventManager ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(ManagerInterface::class);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function createCustomerAccessToken($username, $password)
{
@@ -86,6 +99,7 @@ public function createCustomerAccessToken($username, $password)
)
);
}
+ $this->eventManager->dispatch('customer_login', ['customer' => $customerDataObject]);
$this->getRequestThrottler()->resetAuthenticationFailuresCount($username, RequestThrottler::USER_TYPE_CUSTOMER);
return $this->tokenModelFactory->create()->createCustomerToken($customerDataObject->getId())->getToken();
}
diff --git a/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php b/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php
index 1a7c819343294..1bc7d4247080f 100644
--- a/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php
+++ b/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php
@@ -32,6 +32,9 @@ class CustomerTokenServiceTest extends \PHPUnit\Framework\TestCase
/** @var \Magento\Integration\Model\Oauth\Token|\PHPUnit_Framework_MockObject_MockObject */
private $_tokenMock;
+ /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */
+ protected $manager;
+
protected function setUp()
{
$this->_tokenFactoryMock = $this->getMockBuilder(\Magento\Integration\Model\Oauth\TokenFactory::class)
@@ -67,11 +70,14 @@ protected function setUp()
\Magento\Integration\Model\CredentialsValidator::class
)->disableOriginalConstructor()->getMock();
+ $this->manager = $this->createMock(\Magento\Framework\Event\ManagerInterface::class);
+
$this->_tokenService = new \Magento\Integration\Model\CustomerTokenService(
$this->_tokenFactoryMock,
$this->_accountManagementMock,
$this->_tokenModelCollectionFactoryMock,
- $this->validatorHelperMock
+ $this->validatorHelperMock,
+ $this->manager
);
}
diff --git a/app/code/Magento/Integration/etc/webapi_rest/events.xml b/app/code/Magento/Integration/etc/webapi_rest/events.xml
new file mode 100644
index 0000000000000..e978698734277
--- /dev/null
+++ b/app/code/Magento/Integration/etc/webapi_rest/events.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Integration/etc/webapi_soap/events.xml b/app/code/Magento/Integration/etc/webapi_soap/events.xml
new file mode 100644
index 0000000000000..e978698734277
--- /dev/null
+++ b/app/code/Magento/Integration/etc/webapi_soap/events.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php
index 84ee5285a735b..d1df064f57140 100644
--- a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php
+++ b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php
@@ -876,7 +876,7 @@ private function logExceptions(array $exceptionList)
*/
public function save()
{
- $this->getQuote()->collectTotals();
+ $this->getQuote()->setTotalsCollectedFlag(false)->collectTotals();
$this->quoteRepository->save($this->getQuote());
return $this;
}
@@ -899,13 +899,23 @@ public function reset()
*/
public function validateMinimumAmount()
{
- return !($this->_scopeConfig->isSetFlag(
+ $minimumOrderActive = $this->_scopeConfig->isSetFlag(
'sales/minimum_order/active',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
- ) && $this->_scopeConfig->isSetFlag(
+ );
+
+ $minimumOrderMultiFlag = $this->_scopeConfig->isSetFlag(
'sales/minimum_order/multi_address',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
- ) && !$this->getQuote()->validateMinimumAmount());
+ );
+
+ if ($minimumOrderMultiFlag) {
+ $result = !($minimumOrderActive && !$this->getQuote()->validateMinimumAmount());
+ } else {
+ $result = !($minimumOrderActive && !$this->validateMinimumAmountForAddressItems());
+ }
+
+ return $result;
}
/**
@@ -1123,6 +1133,44 @@ private function getShippingAssignmentProcessor()
return $this->shippingAssignmentProcessor;
}
+ /**
+ * Validate minimum amount for "Checkout with Multiple Addresses" when
+ * "Validate Each Address Separately in Multi-address Checkout" is No.
+ *
+ * @return bool
+ */
+ private function validateMinimumAmountForAddressItems()
+ {
+ $result = true;
+ $storeId = $this->getQuote()->getStoreId();
+
+ $minAmount = $this->_scopeConfig->getValue(
+ 'sales/minimum_order/amount',
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
+ $storeId
+ );
+ $taxInclude = $this->_scopeConfig->getValue(
+ 'sales/minimum_order/tax_including',
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
+ $storeId
+ );
+
+ $this->getQuote()->collectTotals();
+ $addresses = $this->getQuote()->getAllAddresses();
+
+ $baseTotal = 0;
+ foreach ($addresses as $address) {
+ $taxes = $taxInclude ? $address->getBaseTaxAmount() : 0;
+ $baseTotal += $address->getBaseSubtotalWithDiscount() + $taxes;
+ }
+
+ if ($baseTotal < $minAmount) {
+ $result = false;
+ }
+
+ return $result;
+ }
+
/**
* Remove successfully placed items from quote.
*
diff --git a/app/code/Magento/Multishipping/Test/Unit/Model/Checkout/Type/MultishippingTest.php b/app/code/Magento/Multishipping/Test/Unit/Model/Checkout/Type/MultishippingTest.php
index 853d3e3046be6..02bc966873774 100644
--- a/app/code/Magento/Multishipping/Test/Unit/Model/Checkout/Type/MultishippingTest.php
+++ b/app/code/Magento/Multishipping/Test/Unit/Model/Checkout/Type/MultishippingTest.php
@@ -167,13 +167,18 @@ class MultishippingTest extends \PHPUnit\Framework\TestCase
*/
private $sessionMock;
+ /**
+ * @var PHPUnit_Framework_MockObject_MockObject
+ */
+ private $scopeConfigMock;
+
protected function setUp()
{
$this->checkoutSessionMock = $this->createSimpleMock(Session::class);
$this->customerSessionMock = $this->createSimpleMock(CustomerSession::class);
$this->orderFactoryMock = $this->createSimpleMock(OrderFactory::class);
$eventManagerMock = $this->createSimpleMock(ManagerInterface::class);
- $scopeConfigMock = $this->createSimpleMock(ScopeConfigInterface::class);
+ $this->scopeConfigMock = $this->createSimpleMock(ScopeConfigInterface::class);
$this->sessionMock = $this->createSimpleMock(Generic::class);
$addressFactoryMock = $this->createSimpleMock(AddressFactory::class);
$this->toOrderMock = $this->createSimpleMock(ToOrder::class);
@@ -224,7 +229,7 @@ protected function setUp()
$this->orderFactoryMock,
$this->addressRepositoryMock,
$eventManagerMock,
- $scopeConfigMock,
+ $this->scopeConfigMock,
$this->sessionMock,
$addressFactoryMock,
$this->toOrderMock,
@@ -277,6 +282,10 @@ public function testSetShippingItemsInformation()
->willReturn(null);
$this->quoteMock->expects($this->atLeastOnce())->method('getAllItems')->willReturn([]);
+ $this->quoteMock->expects($this->once())
+ ->method('__call')
+ ->with('setTotalsCollectedFlag', [false])
+ ->willReturnSelf();
$this->filterBuilderMock->expects($this->atLeastOnce())->method('setField')->willReturnSelf();
$this->filterBuilderMock->expects($this->atLeastOnce())->method('setValue')->willReturnSelf();
@@ -416,6 +425,10 @@ public function testSetShippingMethods()
$addressMock->expects($this->once())->method('getId')->willReturn($addressId);
$this->quoteMock->expects($this->once())->method('getAllShippingAddresses')->willReturn([$addressMock]);
$addressMock->expects($this->once())->method('setShippingMethod')->with($methodsArray[$addressId]);
+ $this->quoteMock->expects($this->once())
+ ->method('__call')
+ ->with('setTotalsCollectedFlag', [false])
+ ->willReturnSelf();
$this->mockShippingAssignment();
@@ -809,4 +822,37 @@ private function createSimpleMock($className)
->disableOriginalConstructor()
->getMock();
}
+
+ public function testValidateMinimumAmountMultiAddressTrue()
+ {
+ $this->scopeConfigMock->expects($this->exactly(2))->method('isSetFlag')->withConsecutive(
+ ['sales/minimum_order/active', \Magento\Store\Model\ScopeInterface::SCOPE_STORE],
+ ['sales/minimum_order/multi_address', \Magento\Store\Model\ScopeInterface::SCOPE_STORE]
+ )->willReturnOnConsecutiveCalls(true, true);
+
+ $this->checkoutSessionMock->expects($this->atLeastOnce())->method('getQuote')->willReturn($this->quoteMock);
+ $this->quoteMock->expects($this->once())->method('validateMinimumAmount')->willReturn(false);
+ $this->assertFalse($this->model->validateMinimumAmount());
+ }
+
+ public function testValidateMinimumAmountMultiAddressFalse()
+ {
+ $addressMock = $this->createMock(\Magento\Quote\Model\Quote\Address::class);
+ $this->scopeConfigMock->expects($this->exactly(2))->method('isSetFlag')->withConsecutive(
+ ['sales/minimum_order/active', \Magento\Store\Model\ScopeInterface::SCOPE_STORE],
+ ['sales/minimum_order/multi_address', \Magento\Store\Model\ScopeInterface::SCOPE_STORE]
+ )->willReturnOnConsecutiveCalls(true, false);
+
+ $this->scopeConfigMock->expects($this->exactly(2))->method('getValue')->withConsecutive(
+ ['sales/minimum_order/amount', \Magento\Store\Model\ScopeInterface::SCOPE_STORE],
+ ['sales/minimum_order/tax_including', \Magento\Store\Model\ScopeInterface::SCOPE_STORE]
+ )->willReturnOnConsecutiveCalls(100, false);
+
+ $this->checkoutSessionMock->expects($this->atLeastOnce())->method('getQuote')->willReturn($this->quoteMock);
+ $this->quoteMock->expects($this->once())->method('getStoreId')->willReturn(1);
+ $this->quoteMock->expects($this->once())->method('getAllAddresses')->willReturn([$addressMock]);
+ $addressMock->expects($this->once())->method('getBaseSubtotalWithDiscount')->willReturn(101);
+
+ $this->assertTrue($this->model->validateMinimumAmount());
+ }
}
diff --git a/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml b/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml
index 0f96f7cdb708a..a29013cc71722 100644
--- a/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml
+++ b/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml
@@ -14,17 +14,6 @@
* @var $block \Magento\Multishipping\Block\Checkout\Addresses
*/
?>
-