diff --git a/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php index 8721b600ed396..c2379e9dff062 100644 --- a/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php +++ b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php @@ -10,6 +10,10 @@ use Magento\Framework\Model\ResourceModel\Db\Context; use Magento\Framework\EntityManager\MetadataPool; use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver as TableResolver; +use Magento\Framework\Search\Request\Dimension; +use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; /** * @api @@ -29,22 +33,30 @@ class Index extends AbstractDb */ protected $metadataPool; + /** + * @var TableResolver + */ + private $tableResolver; + /** * Index constructor. * @param Context $context * @param StoreManagerInterface $storeManager * @param MetadataPool $metadataPool * @param null $connectionName + * @param TableResolver|null $tableResolver */ public function __construct( Context $context, StoreManagerInterface $storeManager, MetadataPool $metadataPool, - $connectionName = null + $connectionName = null, + TableResolver $tableResolver = null ) { parent::__construct($context, $connectionName); $this->storeManager = $storeManager; $this->metadataPool = $metadataPool; + $this->tableResolver = $tableResolver ?: ObjectManager::getInstance()->get(TableResolver::class); } /** @@ -116,8 +128,17 @@ public function getCategoryProductIndexData($storeId = null, $productIds = null) { $connection = $this->getConnection(); + $catalogCategoryProductDimension = new Dimension(\Magento\Store\Model\Store::ENTITY, $storeId); + + $catalogCategoryProductTableName = $this->tableResolver->resolve( + AbstractAction::MAIN_INDEX_TABLE, + [ + $catalogCategoryProductDimension + ] + ); + $select = $connection->select()->from( - [$this->getTable('catalog_category_product_index')], + [$catalogCategoryProductTableName], ['category_id', 'product_id', 'position', 'store_id'] )->where( 'store_id = ?', diff --git a/app/code/Magento/Backend/Block/Template.php b/app/code/Magento/Backend/Block/Template.php index d0f39b54c1492..3ae4451a2592f 100644 --- a/app/code/Magento/Backend/Block/Template.php +++ b/app/code/Magento/Backend/Block/Template.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Backend\Block; /** @@ -17,10 +20,12 @@ * Example: * * - * My\Module\ViewModel\Custom + * My\Module\ViewModel\Custom * * * + * Your class object can then be accessed by doing $block->getViewModel() + * * @api * @SuppressWarnings(PHPMD.NumberOfChildren) * @since 100.0.2 diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php index 320713f8b57c4..a611e91f32f00 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php @@ -65,7 +65,7 @@ public function render(\Magento\Framework\DataObject $row) */ protected function _getCheckboxHtml($value, $checked) { - $id = 'id_' . rand(0, 999); + $id = 'id_' . random_int(0, 999); $html = ''; $html .= 'Magento\Config\Model\Config\Source\Yesno - Add Block Names to Hints + Add Block Class Type to Hints Magento\Config\Model\Config\Source\Yesno diff --git a/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php b/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php index f68b7eca047f5..2a9923a333cef 100644 --- a/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php +++ b/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php @@ -66,6 +66,13 @@ public function __construct( public function beforeSave() { $value = $this->getValue(); + if (!is_array($value)) { + try { + $value = $this->serializer->unserialize($value); + } catch (\InvalidArgumentException $e) { + $value = []; + } + } $result = []; foreach ($value as $data) { if (empty($data['country_id']) || empty($data['cc_types'])) { diff --git a/app/code/Magento/Bundle/Model/Option/SaveAction.php b/app/code/Magento/Bundle/Model/Option/SaveAction.php new file mode 100644 index 0000000000000..00c5b05d532f5 --- /dev/null +++ b/app/code/Magento/Bundle/Model/Option/SaveAction.php @@ -0,0 +1,195 @@ +optionResource = $optionResource; + $this->metadataPool = $metadataPool; + $this->type = $type; + $this->linkManagement = $linkManagement; + } + + /** + * Manage the logic of saving a bundle option, including the coalescence of its parent product data. + * + * @param ProductInterface $bundleProduct + * @param OptionInterface $option + * @return OptionInterface + * @throws CouldNotSaveException + * @throws \Exception + */ + public function save(ProductInterface $bundleProduct, OptionInterface $option) + { + $metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + + $option->setStoreId($bundleProduct->getStoreId()); + $parentId = $bundleProduct->getData($metadata->getLinkField()); + $option->setParentId($parentId); + + $optionId = $option->getOptionId(); + $linksToAdd = []; + $optionCollection = $this->type->getOptionsCollection($bundleProduct); + $optionCollection->setIdFilter($option->getOptionId()); + $optionCollection->setProductLinkFilter($parentId); + + /** @var \Magento\Bundle\Model\Option $existingOption */ + $existingOption = $optionCollection->getFirstItem(); + if (!$optionId || $existingOption->getParentId() != $parentId) { + //If option ID is empty or existing option's parent ID is different + //we'd need a new ID for the option. + $option->setOptionId(null); + $option->setDefaultTitle($option->getTitle()); + if (is_array($option->getProductLinks())) { + $linksToAdd = $option->getProductLinks(); + } + } else { + if (!$existingOption->getOptionId()) { + throw new NoSuchEntityException( + __("The option that was requested doesn't exist. Verify the entity and try again.") + ); + } + + $option->setData(array_merge($existingOption->getData(), $option->getData())); + $this->updateOptionSelection($bundleProduct, $option); + } + + try { + $this->optionResource->save($option); + } catch (\Exception $e) { + throw new CouldNotSaveException(__("The option couldn't be saved."), $e); + } + + /** @var \Magento\Bundle\Api\Data\LinkInterface $linkedProduct */ + foreach ($linksToAdd as $linkedProduct) { + $this->linkManagement->addChild($bundleProduct, $option->getOptionId(), $linkedProduct); + } + + $bundleProduct->setIsRelationsChanged(true); + + return $option; + } + + /** + * Update option selections + * + * @param \Magento\Catalog\Api\Data\ProductInterface $product + * @param \Magento\Bundle\Api\Data\OptionInterface $option + * @return void + */ + private function updateOptionSelection(ProductInterface $product, OptionInterface $option) + { + $optionId = $option->getOptionId(); + $existingLinks = $this->linkManagement->getChildren($product->getSku(), $optionId); + $linksToAdd = []; + $linksToUpdate = []; + $linksToDelete = []; + if (is_array($option->getProductLinks())) { + $productLinks = $option->getProductLinks(); + foreach ($productLinks as $productLink) { + if (!$productLink->getId() && !$productLink->getSelectionId()) { + $linksToAdd[] = $productLink; + } else { + $linksToUpdate[] = $productLink; + } + } + /** @var \Magento\Bundle\Api\Data\LinkInterface[] $linksToDelete */ + $linksToDelete = $this->compareLinks($existingLinks, $linksToUpdate); + } + foreach ($linksToUpdate as $linkedProduct) { + $this->linkManagement->saveChild($product->getSku(), $linkedProduct); + } + foreach ($linksToDelete as $linkedProduct) { + $this->linkManagement->removeChild( + $product->getSku(), + $option->getOptionId(), + $linkedProduct->getSku() + ); + } + foreach ($linksToAdd as $linkedProduct) { + $this->linkManagement->addChild($product, $option->getOptionId(), $linkedProduct); + } + } + + /** + * Compute the difference between given arrays. + * + * @param \Magento\Bundle\Api\Data\LinkInterface[] $firstArray + * @param \Magento\Bundle\Api\Data\LinkInterface[] $secondArray + * + * @return array + */ + private function compareLinks(array $firstArray, array $secondArray) + { + $result = []; + + $firstArrayIds = []; + $firstArrayMap = []; + + $secondArrayIds = []; + + foreach ($firstArray as $item) { + $firstArrayIds[] = $item->getId(); + + $firstArrayMap[$item->getId()] = $item; + } + + foreach ($secondArray as $item) { + $secondArrayIds[] = $item->getId(); + } + + foreach (array_diff($firstArrayIds, $secondArrayIds) as $id) { + $result[] = $firstArrayMap[$id]; + } + + return $result; + } +} diff --git a/app/code/Magento/Bundle/Model/OptionRepository.php b/app/code/Magento/Bundle/Model/OptionRepository.php index b5e5244a11fda..a70f4af56e989 100644 --- a/app/code/Magento/Bundle/Model/OptionRepository.php +++ b/app/code/Magento/Bundle/Model/OptionRepository.php @@ -7,14 +7,14 @@ namespace Magento\Bundle\Model; +use Magento\Bundle\Model\Option\SaveAction; use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; /** + * Repository for performing CRUD operations for a bundle product's options. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class OptionRepository implements \Magento\Bundle\Api\ProductOptionRepositoryInterface @@ -39,11 +39,6 @@ class OptionRepository implements \Magento\Bundle\Api\ProductOptionRepositoryInt */ protected $optionResource; - /** - * @var \Magento\Store\Model\StoreManager - */ - protected $storeManager; - /** * @var \Magento\Bundle\Api\ProductLinkManagementInterface */ @@ -54,53 +49,44 @@ class OptionRepository implements \Magento\Bundle\Api\ProductOptionRepositoryInt */ protected $productOptionList; - /** - * @var Product\LinksList - */ - protected $linkList; - /** * @var \Magento\Framework\Api\DataObjectHelper */ protected $dataObjectHelper; /** - * @var \Magento\Framework\EntityManager\MetadataPool + * @var SaveAction */ - private $metadataPool; + private $optionSave; /** * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository * @param Product\Type $type * @param \Magento\Bundle\Api\Data\OptionInterfaceFactory $optionFactory * @param \Magento\Bundle\Model\ResourceModel\Option $optionResource - * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Bundle\Api\ProductLinkManagementInterface $linkManagement * @param Product\OptionList $productOptionList - * @param Product\LinksList $linkList * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper - * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * @param SaveAction $optionSave */ public function __construct( \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, \Magento\Bundle\Model\Product\Type $type, \Magento\Bundle\Api\Data\OptionInterfaceFactory $optionFactory, \Magento\Bundle\Model\ResourceModel\Option $optionResource, - \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Bundle\Api\ProductLinkManagementInterface $linkManagement, \Magento\Bundle\Model\Product\OptionList $productOptionList, - \Magento\Bundle\Model\Product\LinksList $linkList, - \Magento\Framework\Api\DataObjectHelper $dataObjectHelper + \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, + SaveAction $optionSave ) { $this->productRepository = $productRepository; $this->type = $type; $this->optionFactory = $optionFactory; $this->optionResource = $optionResource; - $this->storeManager = $storeManager; $this->linkManagement = $linkManagement; $this->productOptionList = $productOptionList; - $this->linkList = $linkList; $this->dataObjectHelper = $dataObjectHelper; + $this->optionSave = $optionSave; } /** @@ -118,7 +104,7 @@ public function get($sku, $optionId) ); } - $productLinks = $this->linkList->getItems($product, $optionId); + $productLinks = $this->linkManagement->getChildren($product->getSku(), $optionId); /** @var \Magento\Bundle\Api\Data\OptionInterface $option */ $optionDataObject = $this->optionFactory->create(); @@ -177,7 +163,9 @@ public function deleteById($sku, $optionId) $product = $this->getProduct($sku); $optionCollection = $this->type->getOptionsCollection($product); $optionCollection->setIdFilter($optionId); - return $this->delete($optionCollection->getFirstItem()); + $hasBeenDeleted = $this->delete($optionCollection->getFirstItem()); + + return $hasBeenDeleted; } /** @@ -187,51 +175,12 @@ public function save( \Magento\Catalog\Api\Data\ProductInterface $product, \Magento\Bundle\Api\Data\OptionInterface $option ) { - $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); - - $option->setStoreId($product->getStoreId()); - $parentId = $product->getData($metadata->getLinkField()); - $option->setParentId($parentId); - - $optionId = $option->getOptionId(); - $linksToAdd = []; - $optionCollection = $this->type->getOptionsCollection($product); - $optionCollection->setIdFilter($option->getOptionId()); - $optionCollection->setProductLinkFilter($parentId); - - /** @var \Magento\Bundle\Model\Option $existingOption */ - $existingOption = $optionCollection->getFirstItem(); - if (!$optionId || $existingOption->getParentId() != $parentId) { - //If option ID is empty or existing option's parent ID is different - //we'd need a new ID for the option. - $option->setOptionId(null); - $option->setDefaultTitle($option->getTitle()); - if (is_array($option->getProductLinks())) { - $linksToAdd = $option->getProductLinks(); - } - } else { - if (!$existingOption->getOptionId()) { - throw new NoSuchEntityException( - __("The option that was requested doesn't exist. Verify the entity and try again.") - ); - } + $savedOption = $this->optionSave->save($product, $option); - $option->setData(array_merge($existingOption->getData(), $option->getData())); - $this->updateOptionSelection($product, $option); - } + $productToSave = $this->productRepository->get($product->getSku()); + $this->productRepository->save($productToSave); - try { - $this->optionResource->save($option); - } catch (\Exception $e) { - throw new CouldNotSaveException(__("The option couldn't be saved."), $e); - } - - /** @var \Magento\Bundle\Api\Data\LinkInterface $linkedProduct */ - foreach ($linksToAdd as $linkedProduct) { - $this->linkManagement->addChild($product, $option->getOptionId(), $linkedProduct); - } - $product->setIsRelationsChanged(true); - return $option->getOptionId(); + return $savedOption->getOptionId(); } /** @@ -325,16 +274,4 @@ private function compareLinks(array $firstArray, array $secondArray) return $result; } - - /** - * Get MetadataPool instance - * @return MetadataPool - */ - private function getMetadataPool() - { - if (!$this->metadataPool) { - $this->metadataPool = ObjectManager::getInstance()->get(MetadataPool::class); - } - return $this->metadataPool; - } } diff --git a/app/code/Magento/Bundle/Model/Product/SaveHandler.php b/app/code/Magento/Bundle/Model/Product/SaveHandler.php index 8517cec6aff6d..e5fa688c7fece 100644 --- a/app/code/Magento/Bundle/Model/Product/SaveHandler.php +++ b/app/code/Magento/Bundle/Model/Product/SaveHandler.php @@ -6,6 +6,7 @@ namespace Magento\Bundle\Model\Product; use Magento\Bundle\Api\Data\OptionInterface; +use Magento\Bundle\Model\Option\SaveAction; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Bundle\Api\ProductOptionRepositoryInterface as OptionRepository; use Magento\Bundle\Api\ProductLinkManagementInterface; @@ -33,19 +34,26 @@ class SaveHandler implements ExtensionInterface */ private $metadataPool; + /** + * @var SaveAction + */ + private $optionSave; + /** * @param OptionRepository $optionRepository * @param ProductLinkManagementInterface $productLinkManagement + * @param SaveAction $optionSave * @param MetadataPool|null $metadataPool */ public function __construct( OptionRepository $optionRepository, ProductLinkManagementInterface $productLinkManagement, + SaveAction $optionSave, MetadataPool $metadataPool = null ) { $this->optionRepository = $optionRepository; $this->productLinkManagement = $productLinkManagement; - + $this->optionSave = $optionSave; $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class); } @@ -103,7 +111,7 @@ public function execute($entity, $arguments = []) } //Saving active options. foreach ($options as $option) { - $this->optionRepository->save($entity, $option); + $this->optionSave->save($entity, $option); } $entity->setCopyFromView(false); diff --git a/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php b/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php index 7549d402a57ff..b4a466b413af0 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php @@ -14,11 +14,6 @@ */ class OptionRepositoryTest extends \PHPUnit\Framework\TestCase { - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $metadataPoolMock; - /** * @var \Magento\Bundle\Model\OptionRepository */ @@ -67,12 +62,12 @@ class OptionRepositoryTest extends \PHPUnit\Framework\TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $linkListMock; + protected $dataObjectHelperMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Bundle\Model\Option\SaveAction|\PHPUnit_Framework_MockObject_MockObject */ - protected $dataObjectHelperMock; + private $optionSaveActionMock; protected function setUp() { @@ -94,24 +89,18 @@ protected function setUp() $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $this->linkManagementMock = $this->createMock(\Magento\Bundle\Api\ProductLinkManagementInterface::class); $this->optionListMock = $this->createMock(\Magento\Bundle\Model\Product\OptionList::class); - $this->linkListMock = $this->createMock(\Magento\Bundle\Model\Product\LinksList::class); - $this->metadataPoolMock = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); + $this->optionSaveActionMock = $this->createMock(\Magento\Bundle\Model\Option\SaveAction::class); $this->model = new OptionRepository( $this->productRepositoryMock, $this->typeMock, $this->optionFactoryMock, $this->optionResourceMock, - $this->storeManagerMock, $this->linkManagementMock, $this->optionListMock, - $this->linkListMock, - $this->dataObjectHelperMock + $this->dataObjectHelperMock, + $this->optionSaveActionMock ); - $refClass = new \ReflectionClass(OptionRepository::class); - $refProperty = $refClass->getProperty('metadataPool'); - $refProperty->setAccessible(true); - $refProperty->setValue($this->model, $this->metadataPoolMock); } /** @@ -174,7 +163,7 @@ public function testGet() $productMock->expects($this->once()) ->method('getTypeId') ->willReturn(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE); - $productMock->expects($this->once())->method('getSku')->willReturn($productSku); + $productMock->expects($this->exactly(2))->method('getSku')->willReturn($productSku); $this->productRepositoryMock->expects($this->once()) ->method('get') @@ -194,7 +183,6 @@ public function testGet() $optionMock->expects($this->once())->method('getData')->willReturn($optionData); $linkMock = ['item']; - $this->linkListMock->expects($this->once())->method('getItems')->with($productMock, 100)->willReturn($linkMock); $newOptionMock = $this->createMock(\Magento\Bundle\Api\Data\OptionInterface::class); $this->dataObjectHelperMock->expects($this->once()) @@ -207,10 +195,10 @@ public function testGet() ->with($optionData['title']) ->willReturnSelf(); $newOptionMock->expects($this->once())->method('setSku')->with()->willReturnSelf(); - $newOptionMock->expects($this->once()) - ->method('setProductLinks') - ->with($linkMock) - ->willReturnSelf(); + $this->linkManagementMock->expects($this->once()) + ->method('getChildren') + ->with($productSku, $optionId) + ->willReturn($linkMock); $this->optionFactoryMock->expects($this->once())->method('create')->willReturn($newOptionMock); @@ -272,172 +260,67 @@ public function testDeleteById() */ public function testSaveExistingOption() { - $productId = 1; - - $storeId = 2; $optionId = 5; - $existingLinkToUpdateId = '23'; + + $productSku = 'sku'; $productMock = $this->createMock(\Magento\Catalog\Model\Product::class); - $productMock->expects($this->once())->method('getData')->willReturn($productId); - $productMock->expects($this->once())->method('getStoreId')->willReturn($storeId); - $optionCollectionMock = $this->getMockBuilder(\Magento\Bundle\Model\ResourceModel\Option\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->typeMock->expects($this->once()) - ->method('getOptionsCollection') - ->with($productMock) - ->willReturn($optionCollectionMock); - $optionCollectionMock->expects($this->once())->method('setIdFilter')->with($optionId)->willReturnSelf(); + $productMock->expects($this->once())->method('getSku')->willReturn($productSku); $optionMock = $this->createPartialMock( \Magento\Bundle\Model\Option::class, ['setStoreId', 'setParentId', 'getProductLinks', 'getOptionId', 'getResource'] ); - $optionCollectionMock->expects($this->once())->method('getFirstItem')->willReturn($optionMock); - $metadataMock = $this->createMock(\Magento\Framework\EntityManager\EntityMetadata::class); - $metadataMock->expects($this->once())->method('getLinkField')->willReturn('product_option'); - - $this->metadataPoolMock->expects($this->once())->method('getMetadata') - ->with(\Magento\Catalog\Api\Data\ProductInterface::class) - ->willReturn($metadataMock); $optionMock->expects($this->atLeastOnce())->method('getOptionId')->willReturn($optionId); - $productLinkUpdate = $this->createMock(\Magento\Bundle\Api\Data\LinkInterface::class); - $productLinkUpdate->expects($this->any())->method('getId')->willReturn($existingLinkToUpdateId); - $productLinkNew = $this->createMock(\Magento\Bundle\Api\Data\LinkInterface::class); - $productLinkNew->expects($this->any())->method('getId')->willReturn(null); - $optionMock->expects($this->exactly(2)) - ->method('getProductLinks') - ->willReturn([$productLinkUpdate, $productLinkNew]); + $this->optionSaveActionMock->expects($this->once())->method('save')->with($productMock, $optionMock) + ->willReturn($optionMock); + + $this->productRepositoryMock + ->expects($this->once()) + ->method('get') + ->with($productSku) + ->willReturn($productMock); - $this->linkManagementMock->expects($this->exactly(2)) - ->method('addChild') + $this->productRepositoryMock + ->expects($this->once()) + ->method('save') ->with($productMock); + $this->assertEquals($optionId, $this->model->save($productMock, $optionMock)); } public function testSaveNewOption() { - $productId = 1; - $productSku = 'bundle_sku'; - $storeId = 2; $optionId = 5; - $productMock = $this->createMock(\Magento\Catalog\Model\Product::class); - $productMock->expects($this->once())->method('getData')->willReturn($productId); - $productMock->expects($this->once())->method('getStoreId')->willReturn($storeId); - $productMock->expects($this->any())->method('getSku')->willReturn($productSku); + $productSku = 'sku'; - $optionCollectionMock = $this->getMockBuilder(\Magento\Bundle\Model\ResourceModel\Option\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->typeMock->expects($this->once()) - ->method('getOptionsCollection') - ->with($productMock) - ->willReturn($optionCollectionMock); - $optionCollectionMock->expects($this->once())->method('setIdFilter')->with($optionId)->willReturnSelf(); + $productMock = $this->createMock(\Magento\Catalog\Model\Product::class); + $productMock->expects($this->once())->method('getSku')->willReturn($productSku); $optionMock = $this->createPartialMock( \Magento\Bundle\Model\Option::class, - [ - 'setStoreId', - 'setParentId', - 'getProductLinks', - 'getOptionId', - 'setOptionId', - 'setDefaultTitle', - 'getTitle', - 'getResource' - ] - ); - $optionCollectionMock->expects($this->once())->method('getFirstItem')->willReturn($optionMock); - $metadataMock = $this->createMock( - \Magento\Framework\EntityManager\EntityMetadata::class + ['setStoreId', 'setParentId', 'getProductLinks', 'getOptionId', 'getResource'] ); - $metadataMock->expects($this->once())->method('getLinkField')->willReturn('product_option'); - - $this->metadataPoolMock->expects($this->once())->method('getMetadata') - ->with(\Magento\Catalog\Api\Data\ProductInterface::class) - ->willReturn($metadataMock); - $optionMock->method('getOptionId') - ->willReturn($optionId); - - $productLink1 = $this->createMock(\Magento\Bundle\Api\Data\LinkInterface::class); - $productLink2 = $this->createMock(\Magento\Bundle\Api\Data\LinkInterface::class); - $optionMock->expects($this->exactly(2)) - ->method('getProductLinks') - ->willReturn([$productLink1, $productLink2]); - - $this->optionResourceMock->expects($this->once())->method('save')->with($optionMock)->willReturnSelf(); - $this->linkManagementMock->expects($this->at(0)) - ->method('addChild') - ->with($productMock, $optionId, $productLink1); - $this->linkManagementMock->expects($this->at(1)) - ->method('addChild') - ->with($productMock, $optionId, $productLink2); - $this->assertEquals($optionId, $this->model->save($productMock, $optionMock)); - } - /** - * @expectedException \Magento\Framework\Exception\CouldNotSaveException - * @expectedExceptionMessage The option couldn't be saved. - */ - public function testSaveCanNotSave() - { - $productId = 1; - $productSku = 'bundle_sku'; - $storeId = 2; - $optionId = 5; + $optionMock->expects($this->atLeastOnce())->method('getOptionId')->willReturn($optionId); - $productMock = $this->createMock(\Magento\Catalog\Model\Product::class); - $productMock->expects($this->once())->method('getData')->willReturn($productId); - $productMock->expects($this->once())->method('getStoreId')->willReturn($storeId); - $productMock->expects($this->any())->method('getSku')->willReturn($productSku); + $this->optionSaveActionMock->expects($this->once())->method('save')->with($productMock, $optionMock) + ->willReturn($optionMock); - $optionCollectionMock = $this->getMockBuilder(\Magento\Bundle\Model\ResourceModel\Option\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->typeMock->expects($this->once()) - ->method('getOptionsCollection') - ->with($productMock) - ->willReturn($optionCollectionMock); - $optionCollectionMock->expects($this->once())->method('setIdFilter')->with($optionId)->willReturnSelf(); + $this->productRepositoryMock + ->expects($this->once()) + ->method('get') + ->with($productSku) + ->willReturn($productMock); - $optionMock = $this->createPartialMock( - \Magento\Bundle\Model\Option::class, - [ - 'setStoreId', - 'setParentId', - 'getProductLinks', - 'getOptionId', - 'setOptionId', - 'setDefaultTitle', - 'getTitle', - 'getResource' - ] - ); - $optionCollectionMock->expects($this->once())->method('getFirstItem')->willReturn($optionMock); - $metadataMock = $this->createMock( - \Magento\Framework\EntityManager\EntityMetadata::class - ); - $metadataMock->expects($this->once())->method('getLinkField')->willReturn('product_option'); - - $this->metadataPoolMock->expects($this->once())->method('getMetadata') - ->with(\Magento\Catalog\Api\Data\ProductInterface::class) - ->willReturn($metadataMock); - $optionMock->method('getOptionId')->willReturn($optionId); - - $productLink1 = $this->createMock(\Magento\Bundle\Api\Data\LinkInterface::class); - $productLink2 = $this->createMock(\Magento\Bundle\Api\Data\LinkInterface::class); - $optionMock->expects($this->exactly(2)) - ->method('getProductLinks') - ->willReturn([$productLink1, $productLink2]); - - $this->optionResourceMock->expects($this->once())->method('save')->with($optionMock) - ->willThrowException($this->createMock(\Exception::class)); - $this->model->save($productMock, $optionMock); + $this->productRepositoryMock + ->expects($this->once()) + ->method('save') + ->with($productMock); + $this->assertEquals($optionId, $this->model->save($productMock, $optionMock)); } public function testGetList() diff --git a/app/code/Magento/Catalog/Block/Product/ListProduct.php b/app/code/Magento/Catalog/Block/Product/ListProduct.php index ee63d3400ade5..c1b255c762dbb 100644 --- a/app/code/Magento/Catalog/Block/Product/ListProduct.php +++ b/app/code/Magento/Catalog/Block/Product/ListProduct.php @@ -356,7 +356,7 @@ public function getIdentities() * Get post parameters * * @param Product $product - * @return string + * @return array */ public function getAddToCartPostParams(Product $product) { diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php b/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php index 46080ab5c3330..c53691ecada24 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php @@ -689,7 +689,7 @@ public function getWidgetOptionsJson(array $customOptions = []) 'limit' => ToolbarModel::LIMIT_PARAM_NAME, 'modeDefault' => $defaultMode, 'directionDefault' => $this->_direction ?: ProductList::DEFAULT_SORT_DIRECTION, - 'orderDefault' => $this->_productListHelper->getDefaultSortField(), + 'orderDefault' => $this->getOrderField(), 'limitDefault' => $this->_productListHelper->getDefaultLimitPerPageValue($defaultMode), 'url' => $this->getPagerUrl(), ]; diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php index 237168282afae..ee3a6d491e92f 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper; use \Magento\Catalog\Model\Product; @@ -28,14 +31,25 @@ class AttributeFilter public function prepareProductAttributes(Product $product, array $productData, array $useDefaults) { foreach ($productData as $attribute => $value) { - $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === "1"; - if ($value === '' && $considerUseDefaultsAttribute) { - /** @var $product Product */ - if ((bool)$product->getData($attribute) === (bool)$value) { - unset($productData[$attribute]); - } + if ($this->isAttributeShouldNotBeUpdated($product, $useDefaults, $attribute, $value)) { + unset($productData[$attribute]); } } + return $productData; } + + /** + * @param Product $product + * @param $useDefaults + * @param $attribute + * @param $value + * @return bool + */ + private function isAttributeShouldNotBeUpdated(Product $product, $useDefaults, $attribute, $value) : bool + { + $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === "1"; + + return ($value === '' && $considerUseDefaultsAttribute && !$product->getData($attribute)); + } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php index 3270416137b67..6a499883ac723 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php @@ -9,8 +9,8 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; use Magento\Framework\App\ObjectManager; -use Magento\Framework\DB\Query\Generator as QueryGenerator; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Query\Generator as QueryGenerator; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; use Magento\Store\Model\Store; @@ -42,6 +42,7 @@ abstract class AbstractAction /** * Suffix for table to show it is temporary + * @deprecated */ const TEMPORARY_TABLE_SUFFIX = '_tmp'; @@ -106,6 +107,11 @@ abstract class AbstractAction */ protected $metadataPool; + /** + * @var TableMaintainer + */ + protected $tableMaintainer; + /** * @var string * @since 101.0.0 @@ -121,15 +127,17 @@ abstract class AbstractAction * @param ResourceConnection $resource * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Catalog\Model\Config $config - * @param QueryGenerator|null $queryGenerator + * @param QueryGenerator $queryGenerator * @param MetadataPool|null $metadataPool + * @param TableMaintainer|null $tableMaintainer */ public function __construct( \Magento\Framework\App\ResourceConnection $resource, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Catalog\Model\Config $config, QueryGenerator $queryGenerator = null, - MetadataPool $metadataPool = null + MetadataPool $metadataPool = null, + TableMaintainer $tableMaintainer = null ) { $this->resource = $resource; $this->connection = $resource->getConnection(); @@ -137,6 +145,7 @@ public function __construct( $this->config = $config; $this->queryGenerator = $queryGenerator ?: ObjectManager::getInstance()->get(QueryGenerator::class); $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class); + $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); } /** @@ -180,6 +189,7 @@ protected function getTable($table) * The name is switched between 'catalog_category_product_index' and 'catalog_category_product_index_replica' * * @return string + * @deprecated */ protected function getMainTable() { @@ -190,6 +200,7 @@ protected function getMainTable() * Return temporary index table name * * @return string + * @deprecated */ protected function getMainTmpTable() { @@ -198,6 +209,19 @@ protected function getMainTmpTable() : $this->getMainTable(); } + /** + * Return index table name + * + * @param int $storeId + * @return string + */ + protected function getIndexTable($storeId) + { + return $this->useTempTable + ? $this->tableMaintainer->getMainReplicaTable($storeId) + : $this->tableMaintainer->getMainTable($storeId); + } + /** * Return category path by id * @@ -319,7 +343,7 @@ protected function getNonAnchorCategoriesSelect(Store $store) } /** - * Add filtering by child products to select. + * Add filtering by child products to select * * It's used for correct handling of composite products. * This method makes assumption that select already joins `catalog_product_entity` as `cpe`. @@ -422,7 +446,7 @@ protected function reindexNonAnchorCategories(Store $store) $this->connection->query( $this->connection->insertFromSelect( $select, - $this->getMainTmpTable(), + $this->getIndexTable($store->getId()), ['category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'], \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE ) @@ -573,7 +597,7 @@ protected function getTemporaryTreeIndexTableName() if (empty($this->tempTreeIndexTableName)) { $this->tempTreeIndexTableName = $this->connection->getTableName('temp_catalog_category_tree_index') . '_' - . substr(md5(time() . rand(0, 999999999)), 0, 8); + . substr(md5(time() . random_int(0, 999999999)), 0, 8); } return $this->tempTreeIndexTableName; @@ -612,6 +636,12 @@ protected function makeTempCategoryTreeIndex() ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_PRIMARY] ); + $temporaryTable->addIndex( + 'child_id', + ['child_id'], + ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_INDEX] + ); + // Drop the temporary table in case it already exists on this (persistent?) connection. $this->connection->dropTemporaryTable($temporaryName); $this->connection->createTemporaryTable($temporaryTable); @@ -629,25 +659,30 @@ protected function makeTempCategoryTreeIndex() */ protected function fillTempCategoryTreeIndex($temporaryName) { - // This finds all children (cc2) that descend from a parent (cc) by path. - // For example, cc.path may be '1/2', and cc2.path may be '1/2/3/4/5'. - $temporarySelect = $this->connection->select()->from( - ['cc' => $this->getTable('catalog_category_entity')], - ['parent_id' => 'entity_id'] - )->joinInner( - ['cc2' => $this->getTable('catalog_category_entity')], - 'cc2.path LIKE ' . $this->connection->getConcatSql( - [$this->connection->quoteIdentifier('cc.path'), $this->connection->quote('/%')] - ), - ['child_id' => 'entity_id'] + $selects = $this->prepareSelectsByRange( + $this->connection->select() + ->from( + ['c' => $this->getTable('catalog_category_entity')], + ['entity_id', 'path'] + ), + 'entity_id' ); - $this->connection->query( - $temporarySelect->insertFromSelect( - $temporaryName, - ['parent_id', 'child_id'] - ) - ); + foreach ($selects as $select) { + $values = []; + + foreach ($this->connection->fetchAll($select) as $category) { + foreach (explode('/', $category['path']) as $parentId) { + if ($parentId !== $category['entity_id']) { + $values[] = [$parentId, $category['entity_id']]; + } + } + } + + if (count($values) > 0) { + $this->connection->insertArray($temporaryName, ['parent_id', 'child_id'], $values); + } + } } /** @@ -678,7 +713,7 @@ protected function reindexAnchorCategories(Store $store) $this->connection->query( $this->connection->insertFromSelect( $select, - $this->getMainTmpTable(), + $this->getIndexTable($store->getId()), ['category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'], \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE ) @@ -804,7 +839,7 @@ protected function reindexRootCategory(Store $store) $this->connection->query( $this->connection->insertFromSelect( $select, - $this->getMainTmpTable(), + $this->getIndexTable($store->getId()), ['category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'], \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE ) diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php index cde186a7e44af..09dbed350c5e4 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php @@ -88,18 +88,35 @@ public function __construct( } /** - * - * Clear the table we'll be writing de-normalized data into - * to prevent archived data getting in the way of actual data. - * * @return void */ - private function clearCurrentTable() + private function createTables() { - $this->connection->delete( - $this->activeTableSwitcher - ->getAdditionalTableName($this->getMainTable()) - ); + foreach ($this->storeManager->getStores() as $store) { + $this->tableMaintainer->createTablesForStore($store->getId()); + } + } + + /** + * @return void + */ + private function clearReplicaTables() + { + foreach ($this->storeManager->getStores() as $store) { + $this->connection->truncateTable($this->tableMaintainer->getMainReplicaTable($store->getId())); + } + } + + /** + * @return void + */ + private function switchTables() + { + $tablesToSwitch = []; + foreach ($this->storeManager->getStores() as $store) { + $tablesToSwitch[] = $this->tableMaintainer->getMainTable($store->getId()); + } + $this->activeTableSwitcher->switchTable($this->connection, $tablesToSwitch); } /** @@ -109,22 +126,26 @@ private function clearCurrentTable() */ public function execute() { - $this->clearCurrentTable(); + $this->createTables(); + $this->clearReplicaTables(); $this->reindex(); - $this->activeTableSwitcher->switchTable($this->connection, [$this->getMainTable()]); + $this->switchTables(); return $this; } /** - * Publish data from tmp to index + * Publish data from tmp to replica table * + * @param \Magento\Store\Model\Store $store * @return void */ - protected function publishData() + private function publishData($store) { - $select = $this->connection->select()->from($this->getMainTmpTable()); - $columns = array_keys($this->connection->describeTable($this->getMainTable())); - $tableName = $this->activeTableSwitcher->getAdditionalTableName($this->getMainTable()); + $select = $this->connection->select()->from($this->tableMaintainer->getMainTmpTable($store->getId())); + $columns = array_keys( + $this->connection->describeTable($this->tableMaintainer->getMainReplicaTable($store->getId())) + ); + $tableName = $this->tableMaintainer->getMainReplicaTable($store->getId()); $this->connection->query( $this->connection->insertFromSelect( @@ -136,23 +157,13 @@ protected function publishData() ); } - /** - * Clear all index data - * - * @return void - */ - protected function clearTmpData() - { - $this->connection->delete($this->getMainTmpTable()); - } - /** * {@inheritdoc} */ protected function reindexRootCategory(\Magento\Store\Model\Store $store) { if ($this->isIndexRootCategoryNeeded()) { - $this->reindexCategoriesBySelect($this->getAllProducts($store), 'cp.entity_id IN (?)'); + $this->reindexCategoriesBySelect($this->getAllProducts($store), 'cp.entity_id IN (?)', $store); } } @@ -164,7 +175,7 @@ protected function reindexRootCategory(\Magento\Store\Model\Store $store) */ protected function reindexAnchorCategories(\Magento\Store\Model\Store $store) { - $this->reindexCategoriesBySelect($this->getAnchorCategoriesSelect($store), 'ccp.product_id IN (?)'); + $this->reindexCategoriesBySelect($this->getAnchorCategoriesSelect($store), 'ccp.product_id IN (?)', $store); } /** @@ -175,7 +186,7 @@ protected function reindexAnchorCategories(\Magento\Store\Model\Store $store) */ protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store) { - $this->reindexCategoriesBySelect($this->getNonAnchorCategoriesSelect($store), 'ccp.product_id IN (?)'); + $this->reindexCategoriesBySelect($this->getNonAnchorCategoriesSelect($store), 'ccp.product_id IN (?)', $store); } /** @@ -183,12 +194,17 @@ protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store) * * @param \Magento\Framework\DB\Select $basicSelect * @param string $whereCondition + * @param \Magento\Store\Model\Store $store * @return void */ - private function reindexCategoriesBySelect(\Magento\Framework\DB\Select $basicSelect, $whereCondition) + private function reindexCategoriesBySelect(\Magento\Framework\DB\Select $basicSelect, $whereCondition, $store) { + $this->tableMaintainer->createMainTmpTable($store->getId()); + $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); - $columns = array_keys($this->connection->describeTable($this->getMainTmpTable())); + $columns = array_keys( + $this->connection->describeTable($this->tableMaintainer->getMainTmpTable($store->getId())) + ); $this->batchSizeManagement->ensureBatchSize($this->connection, $this->batchRowsCount); $batches = $this->batchProvider->getBatches( $this->connection, @@ -197,7 +213,7 @@ private function reindexCategoriesBySelect(\Magento\Framework\DB\Select $basicSe $this->batchRowsCount ); foreach ($batches as $batch) { - $this->clearTmpData(); + $this->connection->delete($this->tableMaintainer->getMainTmpTable($store->getId())); $resultSelect = clone $basicSelect; $select = $this->connection->select(); $select->distinct(true); @@ -207,12 +223,12 @@ private function reindexCategoriesBySelect(\Magento\Framework\DB\Select $basicSe $this->connection->query( $this->connection->insertFromSelect( $resultSelect, - $this->getMainTmpTable(), + $this->tableMaintainer->getMainTmpTable($store->getId()), $columns, \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE ) ); - $this->publishData(); + $this->publishData($store); } } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Rows.php index 248ec970d2250..3bd4910767587 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Rows.php @@ -36,17 +36,16 @@ public function execute(array $entityIds = [], $useTempTable = false) /** * Return array of all category root IDs + tree root ID * - * @return int[] + * @param \Magento\Store\Model\Store $store + * @return int */ - protected function getRootCategoryIds() + private function getRootCategoryId($store) { - $rootIds = [\Magento\Catalog\Model\Category::TREE_ROOT_ID]; - foreach ($this->storeManager->getStores() as $store) { - if ($this->getPathFromCategoryId($store->getRootCategoryId())) { - $rootIds[] = $store->getRootCategoryId(); - } + $rootId = \Magento\Catalog\Model\Category::TREE_ROOT_ID; + if ($this->getPathFromCategoryId($store->getRootCategoryId())) { + $rootId = $store->getRootCategoryId(); } - return $rootIds; + return $rootId; } /** @@ -54,10 +53,15 @@ protected function getRootCategoryIds() * * @return void */ - protected function removeEntries() + private function removeEntries() { - $removalCategoryIds = array_diff($this->limitationByCategories, $this->getRootCategoryIds()); - $this->connection->delete($this->getMainTable(), ['category_id IN (?)' => $removalCategoryIds]); + foreach ($this->storeManager->getStores() as $store) { + $removalCategoryIds = array_diff($this->limitationByCategories, [$this->getRootCategoryId($store)]); + $this->connection->delete( + $this->getIndexTable($store->getId()), + ['category_id IN (?)' => $removalCategoryIds] + ); + } } /** diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php index 2ee46b3a6096b..9f4e19bf95a8d 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php @@ -9,6 +9,7 @@ use Magento\Framework\Model\ResourceModel\Db\AbstractDb; use Magento\Framework\Model\AbstractModel; use Magento\Catalog\Model\Indexer\Category\Product; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; class StoreGroup { @@ -22,12 +23,21 @@ class StoreGroup */ protected $indexerRegistry; + /** + * @var TableMaintainer + */ + protected $tableMaintainer; + /** * @param IndexerRegistry $indexerRegistry + * @param TableMaintainer $tableMaintainer */ - public function __construct(IndexerRegistry $indexerRegistry) - { + public function __construct( + IndexerRegistry $indexerRegistry, + TableMaintainer $tableMaintainer + ) { $this->indexerRegistry = $indexerRegistry; + $this->tableMaintainer = $tableMaintainer; } /** @@ -73,4 +83,22 @@ protected function validate(AbstractModel $group) return ($group->dataHasChangedFor('website_id') || $group->dataHasChangedFor('root_category_id')) && !$group->isObjectNew(); } + + /** + * Delete catalog_category_product indexer tables for deleted store group + * + * @param AbstractDb $subject + * @param AbstractDb $objectResource + * @param AbstractModel $storeGroup + * + * @return AbstractDb + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $storeGroup) + { + foreach ($storeGroup->getStores() as $store) { + $this->tableMaintainer->dropTablesForStore($store->getId()); + } + return $objectResource; + } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php index f49b685ba6f7f..114d2a94f5b35 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php @@ -5,6 +5,9 @@ */ namespace Magento\Catalog\Model\Indexer\Category\Product\Plugin; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Model\AbstractModel; + class StoreView extends StoreGroup { /** @@ -17,4 +20,38 @@ protected function validate(\Magento\Framework\Model\AbstractModel $store) { return $store->isObjectNew() || $store->dataHasChangedFor('group_id'); } + + /** + * Invalidate catalog_category_product indexer + * + * @param AbstractDb $subject + * @param AbstractDb $objectResource + * @param AbstractModel $store + * + * @return AbstractDb + */ + public function afterSave(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $store = null) + { + if ($store->isObjectNew()) { + $this->tableMaintainer->createTablesForStore($store->getId()); + } + + return parent::afterSave($subject, $objectResource); + } + + /** + * Delete catalog_category_product indexer table for deleted store + * + * @param AbstractDb $subject + * @param AbstractDb $objectResource + * @param AbstractModel $store + * + * @return AbstractDb + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $store) + { + $this->tableMaintainer->dropTablesForStore($store->getId()); + return $objectResource; + } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php new file mode 100644 index 0000000000000..936e6163cbcc5 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php @@ -0,0 +1,74 @@ +storeManager = $storeManager; + $this->tableResolver = $tableResolver; + } + + /** + * Replacing catalog_category_product_index table name on the table name segmented per store + * + * @param ResourceConnection $subject + * @param string $result + * @param string|string[] $modelEntity + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return string + */ + public function afterGetTableName( + \Magento\Framework\App\ResourceConnection $subject, + string $result, + $modelEntity + ) { + if (!is_array($modelEntity) && $modelEntity === AbstractAction::MAIN_INDEX_TABLE) { + $catalogCategoryProductDimension = new Dimension( + \Magento\Store\Model\Store::ENTITY, + $this->storeManager->getStore()->getId() + ); + + $tableName = $this->tableResolver->resolve( + AbstractAction::MAIN_INDEX_TABLE, + [ + $catalogCategoryProductDimension + ] + ); + return $tableName; + } + return $result; + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php new file mode 100644 index 0000000000000..387a8085310e4 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php @@ -0,0 +1,46 @@ +tableMaintainer = $tableMaintainer; + } + + /** + * Delete catalog_category_product indexer tables for deleted website + * + * @param AbstractDb $subject + * @param AbstractDb $objectResource + * @param AbstractModel $website + * + * @return AbstractDb + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $website) + { + foreach ($website->getStoreIds() as $storeId) { + $this->tableMaintainer->dropTablesForStore($storeId); + } + return $objectResource; + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php new file mode 100644 index 0000000000000..d2f8925d09a7b --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php @@ -0,0 +1,210 @@ +resource = $resource; + $this->tableResolver = $tableResolver; + } + + /** + * Get connection + * + * @return AdapterInterface + */ + private function getConnection() + { + if (!isset($this->connection)) { + $this->connection = $this->resource->getConnection(); + } + return $this->connection; + } + + /** + * Return validated table name + * + * @param string|string[] $table + * @return string + */ + private function getTable($table) + { + return $this->resource->getTableName($table); + } + + /** + * Create table based on main table + * + * @param string $mainTableName + * @param string $newTableName + * + * @return void + */ + private function createTable($mainTableName, $newTableName) + { + if (!$this->getConnection()->isTableExists($newTableName)) { + $this->getConnection()->createTable( + $this->getConnection()->createTableByDdl($mainTableName, $newTableName) + ); + } + } + + /** + * Drop table + * + * @param string $tableName + * + * @return void + */ + private function dropTable($tableName) + { + if ($this->getConnection()->isTableExists($tableName)) { + $this->getConnection()->dropTable($tableName); + } + } + + /** + * Return main index table name + * + * @param $storeId + * + * @return string + */ + public function getMainTable(int $storeId) + { + $catalogCategoryProductDimension = new Dimension(\Magento\Store\Model\Store::ENTITY, $storeId); + + return $this->tableResolver->resolve(AbstractAction::MAIN_INDEX_TABLE, [$catalogCategoryProductDimension]); + } + + /** + * Create main and replica index tables for store + * + * @param $storeId + * + * @return void + */ + public function createTablesForStore(int $storeId) + { + $mainTableName = $this->getMainTable($storeId); + //Create index table for store based on on main replica table + //Using main replica table is necessary for backward capability and TableResolver plugin work + $this->createTable( + $this->getTable(AbstractAction::MAIN_INDEX_TABLE . $this->additionalTableSuffix), + $mainTableName + ); + + $mainReplicaTableName = $this->getMainTable($storeId) . $this->additionalTableSuffix; + //Create replica table for store based on main replica table + $this->createTable( + $this->getTable(AbstractAction::MAIN_INDEX_TABLE . $this->additionalTableSuffix), + $mainReplicaTableName + ); + } + + /** + * Drop main and replica index tables for store + * + * @param $storeId + * + * @return void + */ + public function dropTablesForStore(int $storeId) + { + $mainTableName = $this->getMainTable($storeId); + $this->dropTable($mainTableName); + + $mainReplicaTableName = $this->getMainTable($storeId) . $this->additionalTableSuffix; + $this->dropTable($mainReplicaTableName); + } + + /** + * Return replica index table name + * + * @param $storeId + * + * @return string + */ + public function getMainReplicaTable(int $storeId) + { + return $this->getMainTable($storeId) . $this->additionalTableSuffix; + } + + /** + * Create temporary index table for store + * + * @param $storeId + * + * @return void + */ + public function createMainTmpTable(int $storeId) + { + if (!isset($this->mainTmpTable[$storeId])) { + $originTableName = $this->getMainTable($storeId); + $temporaryTableName = $this->getMainTable($storeId) . $this->tmpTableSuffix; + $this->getConnection()->createTemporaryTableLike($temporaryTableName, $originTableName, true); + $this->mainTmpTable[$storeId] = $temporaryTableName; + } + } + + /** + * Return temporary index table name + * + * @param $storeId + * + * @return string + */ + public function getMainTmpTable(int $storeId) + { + return $this->mainTmpTable[$storeId]; + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php index b67660a24ef88..182f04de4ab0e 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php @@ -6,12 +6,17 @@ namespace Magento\Catalog\Model\Indexer\Product\Category\Action; use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Config; use Magento\Catalog\Model\Product; use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Query\Generator as QueryGenerator; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Event\ManagerInterface as EventManagerInterface; use Magento\Framework\Indexer\CacheContext; +use Magento\Store\Model\StoreManagerInterface; /** - * Reindex products categories. * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction @@ -29,32 +34,31 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio private $cacheContext; /** - * @var \Magento\Framework\Event\ManagerInterface|null + * @var EventManagerInterface|null */ private $eventManager; /** - * @param \Magento\Framework\App\ResourceConnection $resource - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Catalog\Model\Config $config - * @param \Magento\Framework\DB\Query\Generator|null $queryGenerator - * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool + * @param ResourceConnection $resource + * @param StoreManagerInterface $storeManager + * @param Config $config + * @param QueryGenerator|null $queryGenerator + * @param MetadataPool|null $metadataPool * @param CacheContext|null $cacheContext - * @param \Magento\Framework\Event\ManagerInterface|null $eventManager + * @param EventManagerInterface|null $eventManager */ public function __construct( - \Magento\Framework\App\ResourceConnection $resource, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Catalog\Model\Config $config, - \Magento\Framework\DB\Query\Generator $queryGenerator = null, - \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, + ResourceConnection $resource, + StoreManagerInterface $storeManager, + Config $config, + QueryGenerator $queryGenerator = null, + MetadataPool $metadataPool = null, CacheContext $cacheContext = null, - \Magento\Framework\Event\ManagerInterface $eventManager = null + EventManagerInterface $eventManager = null ) { parent::__construct($resource, $storeManager, $config, $queryGenerator, $metadataPool); $this->cacheContext = $cacheContext ?: ObjectManager::getInstance()->get(CacheContext::class); - $this->eventManager = $eventManager ?: - ObjectManager::getInstance()->get(\Magento\Framework\Event\ManagerInterface::class); + $this->eventManager = $eventManager ?: ObjectManager::getInstance()->get(EventManagerInterface::class); } /** @@ -152,10 +156,12 @@ private function registerCategories(array $categoryIds) */ protected function removeEntries() { - $this->connection->delete( - $this->getMainTable(), - ['product_id IN (?)' => $this->limitationByProducts] - ); + foreach ($this->storeManager->getStores() as $store) { + $this->connection->delete( + $this->getIndexTable($store->getId()), + ['product_id IN (?)' => $this->limitationByProducts] + ); + }; } /** @@ -167,7 +173,6 @@ protected function removeEntries() protected function getNonAnchorCategoriesSelect(\Magento\Store\Model\Store $store) { $select = parent::getNonAnchorCategoriesSelect($store); - return $select->where('ccp.product_id IN (?) OR relation.child_id IN (?)', $this->limitationByProducts); } @@ -206,19 +211,24 @@ protected function isRangingNeeded() } /** - * Returns a list of category ids which are assigned to product ids in the index. + * Returns a list of category ids which are assigned to product ids in the index * - * @param array $productIds * @return \Magento\Framework\Indexer\CacheContext */ private function getCategoryIdsFromIndex(array $productIds) { - $categoryIds = $this->connection->fetchCol( - $this->connection->select() - ->from($this->getMainTable(), ['category_id']) - ->where('product_id IN (?)', $productIds) - ->distinct() - ); + $categoryIds = []; + foreach ($this->storeManager->getStores() as $store) { + $categoryIds = array_merge( + $categoryIds, + $this->connection->fetchCol( + $this->connection->select() + ->from($this->getIndexTable($store->getId()), ['category_id']) + ->where('product_id IN (?)', $productIds) + ->distinct() + ) + ); + }; $parentCategories = $categoryIds; foreach ($categoryIds as $categoryId) { $parentIds = explode('/', $this->getPathFromCategoryId($categoryId)); diff --git a/app/code/Magento/Catalog/Model/ProductCategoryList.php b/app/code/Magento/Catalog/Model/ProductCategoryList.php index ae875453be938..5bbae772d5c2b 100644 --- a/app/code/Magento/Catalog/Model/ProductCategoryList.php +++ b/app/code/Magento/Catalog/Model/ProductCategoryList.php @@ -5,9 +5,11 @@ */ namespace Magento\Catalog\Model; -use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; use Magento\Framework\DB\Select; use Magento\Framework\DB\Sql\UnionExpression; +use Magento\Framework\App\ObjectManager; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; /** * Provides info about product categories. @@ -29,16 +31,32 @@ class ProductCategoryList */ private $category; + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var TableMaintainer + */ + private $tableMaintainer; + /** * @param ResourceModel\Product $productResource * @param ResourceModel\Category $category + * @param StoreManagerInterface $storeManager + * @param TableMaintainer|null $tableMaintainer */ public function __construct( ResourceModel\Product $productResource, - ResourceModel\Category $category + ResourceModel\Category $category, + StoreManagerInterface $storeManager = null, + TableMaintainer $tableMaintainer = null ) { $this->productResource = $productResource; $this->category = $category; + $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); + $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); } /** @@ -50,14 +68,15 @@ public function __construct( public function getCategoryIds($productId) { if (!isset($this->categoryIdList[$productId])) { + $unionTables[] = $this->getCategorySelect($productId, $this->category->getCategoryProductTable()); + foreach ($this->storeManager->getStores() as $store) { + $unionTables[] = $this->getCategorySelect( + $productId, + $this->tableMaintainer->getMainTable($store->getId()) + ); + } $unionSelect = new UnionExpression( - [ - $this->getCategorySelect($productId, $this->category->getCategoryProductTable()), - $this->getCategorySelect( - $productId, - $this->productResource->getTable(AbstractAction::MAIN_INDEX_TABLE) - ) - ], + $unionTables, Select::SQL_UNION_ALL ); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product.php b/app/code/Magento/Catalog/Model/ResourceModel/Product.php index 81e9473e053b6..d71ec23881982 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product.php @@ -7,6 +7,7 @@ use Magento\Catalog\Model\ResourceModel\Product\Website\Link as ProductWebsiteLink; use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; /** * Product entity resource model @@ -83,6 +84,11 @@ class Product extends AbstractResource */ private $productCategoryLink; + /** + * @var TableMaintainer + */ + private $tableMaintainer; + /** * @param \Magento\Eav\Model\Entity\Context $context * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -94,6 +100,7 @@ class Product extends AbstractResource * @param \Magento\Eav\Model\Entity\TypeFactory $typeFactory * @param \Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes * @param array $data + * @param TableMaintainer|null $tableMaintainer * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -107,7 +114,8 @@ public function __construct( \Magento\Eav\Model\Entity\Attribute\SetFactory $setFactory, \Magento\Eav\Model\Entity\TypeFactory $typeFactory, \Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes, - $data = [] + $data = [], + TableMaintainer $tableMaintainer = null ) { $this->_categoryCollectionFactory = $categoryCollectionFactory; $this->_catalogCategory = $catalogCategory; @@ -122,6 +130,7 @@ public function __construct( $data ); $this->connectionName = 'catalog'; + $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); } /** @@ -366,22 +375,42 @@ public function getAvailableInCategories($object) // fetching all parent IDs, including those are higher on the tree $entityId = (int)$object->getEntityId(); if (!isset($this->availableCategoryIdsCache[$entityId])) { - $this->availableCategoryIdsCache[$entityId] = $this->getConnection()->fetchCol( - $this->getConnection()->select()->distinct()->from( - $this->getTable('catalog_category_product_index'), - ['category_id'] - )->where( - 'product_id = ? AND is_parent = 1', - $entityId - )->where( - 'visibility != ?', - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE - ) + foreach ($this->_storeManager->getStores() as $store) { + $unionTables[] = $this->getAvailableInCategoriesSelect( + $entityId, + $this->tableMaintainer->getMainTable($store->getId()) + ); + } + $unionSelect = new \Magento\Framework\DB\Sql\UnionExpression( + $unionTables, + \Magento\Framework\DB\Select::SQL_UNION_ALL ); + $this->availableCategoryIdsCache[$entityId] = array_unique($this->getConnection()->fetchCol($unionSelect)); } return $this->availableCategoryIdsCache[$entityId]; } + /** + * Returns DB select for available categories. + * + * @param int $entityId + * @param string $tableName + * @return \Magento\Framework\DB\Select + */ + private function getAvailableInCategoriesSelect($entityId, $tableName) + { + return $this->getConnection()->select()->distinct()->from( + $tableName, + ['category_id'] + )->where( + 'product_id = ? AND is_parent = 1', + $entityId + )->where( + 'visibility != ?', + \Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE + ); + } + /** * Get default attribute source model * @@ -402,7 +431,7 @@ public function getDefaultAttributeSourceModel() public function canBeShowInCategory($product, $categoryId) { $select = $this->getConnection()->select()->from( - $this->getTable('catalog_category_product_index'), + $this->tableMaintainer->getMainTable($product->getStoreId()), 'product_id' )->where( 'product_id = ?', diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 0729f2bd1f8f5..0ce67a96a99cc 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -16,6 +16,7 @@ use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; use Magento\Store\Model\Store; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; /** * Product collection @@ -270,6 +271,11 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac */ private $backend; + /** + * @var TableMaintainer + */ + private $tableMaintainer; + /** * Collection constructor * @@ -295,6 +301,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection * @param ProductLimitationFactory|null $productLimitationFactory * @param MetadataPool|null $metadataPool + * @param TableMaintainer|null $tableMaintainer * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -320,7 +327,8 @@ public function __construct( GroupManagementInterface $groupManagement, \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, ProductLimitationFactory $productLimitationFactory = null, - MetadataPool $metadataPool = null + MetadataPool $metadataPool = null, + TableMaintainer $tableMaintainer = null ) { $this->moduleManager = $moduleManager; $this->_catalogProductFlatState = $catalogProductFlatState; @@ -350,6 +358,7 @@ public function __construct( $storeManager, $connection ); + $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); } /** @@ -1193,7 +1202,7 @@ public function getProductCountSelect() )->distinct( false )->join( - ['count_table' => $this->getTable('catalog_category_product_index')], + ['count_table' => $this->tableMaintainer->getMainTable($this->getStoreId())], 'count_table.product_id = e.entity_id', [ 'count_table.category_id', @@ -1967,7 +1976,7 @@ protected function _applyProductLimitations() $this->getSelect()->setPart(\Magento\Framework\DB\Select::FROM, $fromPart); } else { $this->getSelect()->join( - ['cat_index' => $this->getTable('catalog_category_product_index')], + ['cat_index' => $this->tableMaintainer->getMainTable($this->getStoreId())], $joinCond, ['cat_index_position' => 'position'] ); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Url.php b/app/code/Magento/Catalog/Model/ResourceModel/Url.php index 682f5c1f45e31..be95f088a2477 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Url.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Url.php @@ -14,6 +14,7 @@ use Magento\Catalog\Api\Data\CategoryInterface; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; /** * Class Url @@ -101,6 +102,11 @@ class Url extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ protected $metadataPool; + /** + * @var TableMaintainer + */ + private $tableMaintainer; + /** * Url constructor. * @param \Magento\Framework\Model\ResourceModel\Db\Context $context @@ -110,6 +116,7 @@ class Url extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb * @param \Magento\Catalog\Model\Category $catalogCategory * @param \Psr\Log\LoggerInterface $logger * @param null $connectionName + * @param TableMaintainer|null $tableMaintainer */ public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, @@ -118,7 +125,8 @@ public function __construct( Product $productResource, \Magento\Catalog\Model\Category $catalogCategory, \Psr\Log\LoggerInterface $logger, - $connectionName = null + $connectionName = null, + TableMaintainer $tableMaintainer = null ) { $this->_storeManager = $storeManager; $this->_eavConfig = $eavConfig; @@ -126,6 +134,7 @@ public function __construct( $this->_catalogCategory = $catalogCategory; $this->_logger = $logger; parent::__construct($context, $connectionName); + $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); } /** @@ -655,43 +664,52 @@ public function getRewriteByProductStore(array $products) } $connection = $this->getConnection(); - $select = $connection->select()->from( - ['i' => $this->getTable('catalog_category_product_index')], - ['product_id', 'store_id', 'visibility'] - )->joinLeft( - ['u' => $this->getMainTable()], - 'i.product_id = u.entity_id AND i.store_id = u.store_id' - . ' AND u.entity_type = "' . ProductUrlRewriteGenerator::ENTITY_TYPE . '"', - ['request_path'] - )->joinLeft( - ['r' => $this->getTable('catalog_url_rewrite_product_category')], - 'u.url_rewrite_id = r.url_rewrite_id AND r.category_id is NULL', - [] - ); - - $bind = []; + $storesProducts = []; foreach ($products as $productId => $storeId) { - $catId = $this->_storeManager->getStore($storeId)->getRootCategoryId(); - $productBind = 'product_id' . $productId; - $storeBind = 'store_id' . $storeId; - $catBind = 'category_id' . $catId; - $cond = '(' . implode( - ' AND ', - ['i.product_id = :' . $productBind, 'i.store_id = :' . $storeBind, 'i.category_id = :' . $catBind] - ) . ')'; - $bind[$productBind] = $productId; - $bind[$storeBind] = $storeId; - $bind[$catBind] = $catId; - $select->orWhere($cond); + $storesProducts[$storeId][] = $productId; } - $rowSet = $connection->fetchAll($select, $bind); - foreach ($rowSet as $row) { - $result[$row['product_id']] = [ - 'store_id' => $row['store_id'], - 'visibility' => $row['visibility'], - 'url_rewrite' => $row['request_path'], - ]; + foreach ($storesProducts as $storeId => $productIds) { + $select = $connection->select()->from( + ['i' => $this->tableMaintainer->getMainTable($storeId)], + ['product_id', 'store_id', 'visibility'] + )->joinLeft( + ['u' => $this->getMainTable()], + 'i.product_id = u.entity_id AND i.store_id = u.store_id' + . ' AND u.entity_type = "' . ProductUrlRewriteGenerator::ENTITY_TYPE . '"', + ['request_path'] + )->joinLeft( + ['r' => $this->getTable('catalog_url_rewrite_product_category')], + 'u.url_rewrite_id = r.url_rewrite_id AND r.category_id is NULL', + [] + ); + + $bind = []; + foreach ($productIds as $productId) { + $catId = $this->_storeManager->getStore($storeId)->getRootCategoryId(); + $productBind = 'product_id' . $productId; + $storeBind = 'store_id' . $storeId; + $catBind = 'category_id' . $catId; + $bindArray = [ + 'i.product_id = :' . $productBind, + 'i.store_id = :' . $storeBind, + 'i.category_id = :' . $catBind + ]; + $cond = '(' . implode(' AND ', $bindArray) . ')'; + $bind[$productBind] = $productId; + $bind[$storeBind] = $storeId; + $bind[$catBind] = $catId; + $select->orWhere($cond); + } + + $rowSet = $connection->fetchAll($select, $bind); + foreach ($rowSet as $row) { + $result[$row['product_id']] = [ + 'store_id' => $row['store_id'], + 'visibility' => $row['visibility'], + 'url_rewrite' => $row['request_path'], + ]; + } } return $result; diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/EnableSegmentation.php b/app/code/Magento/Catalog/Setup/Patch/Data/EnableSegmentation.php new file mode 100644 index 0000000000000..d7b683a439ff1 --- /dev/null +++ b/app/code/Magento/Catalog/Setup/Patch/Data/EnableSegmentation.php @@ -0,0 +1,90 @@ +moduleDataSetup = $moduleDataSetup; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $this->moduleDataSetup->startSetup(); + $setup = $this->moduleDataSetup; + + $catalogCategoryProductIndexColumns = array_keys( + $setup->getConnection()->describeTable($setup->getTable('catalog_category_product_index')) + ); + $storeSelect = $setup->getConnection()->select()->from($setup->getTable('store'))->where('store_id > 0'); + foreach ($setup->getConnection()->fetchAll($storeSelect) as $store) { + $catalogCategoryProductIndexSelect = $setup->getConnection()->select() + ->from( + $setup->getTable('catalog_category_product_index') + )->where( + 'store_id = ?', + $store['store_id'] + ); + $indexTable = $setup->getTable('catalog_category_product_index') . + '_' . + \Magento\Store\Model\Store::ENTITY . + $store['store_id']; + $setup->getConnection()->query( + $setup->getConnection()->insertFromSelect( + $catalogCategoryProductIndexSelect, + $indexTable, + $catalogCategoryProductIndexColumns, + \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + ) + ); + } + $setup->getConnection()->delete($setup->getTable('catalog_category_product_index')); + $setup->getConnection()->delete($setup->getTable('catalog_category_product_index_replica')); + $setup->getConnection()->delete($setup->getTable('catalog_category_product_index_tmp')); + + $this->moduleDataSetup->endSetup(); + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Catalog/Setup/Patch/Schema/EnableSegmentation.php b/app/code/Magento/Catalog/Setup/Patch/Schema/EnableSegmentation.php new file mode 100644 index 0000000000000..8ae84f9f8e321 --- /dev/null +++ b/app/code/Magento/Catalog/Setup/Patch/Schema/EnableSegmentation.php @@ -0,0 +1,85 @@ +schemaSetup = $schemaSetup; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $this->schemaSetup->startSetup(); + $setup = $this->schemaSetup; + + $storeSelect = $setup->getConnection()->select()->from($setup->getTable('store'))->where('store_id > 0'); + foreach ($setup->getConnection()->fetchAll($storeSelect) as $store) { + $indexTable = $setup->getTable('catalog_category_product_index') . + '_' . + \Magento\Store\Model\Store::ENTITY . + $store['store_id']; + if (!$setup->getConnection()->isTableExists($indexTable)) { + $setup->getConnection()->createTable( + $setup->getConnection()->createTableByDdl( + $setup->getTable('catalog_category_product_index'), + $indexTable + ) + ); + } + if (!$setup->getConnection()->isTableExists($indexTable . '_replica')) { + $setup->getConnection()->createTable( + $setup->getConnection()->createTableByDdl( + $setup->getTable('catalog_category_product_index'), + $indexTable . '_replica' + ) + ); + } + } + + $this->schemaSetup->endSetup(); + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php index 6e3cd6ed30b52..4da831f5257d0 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php @@ -33,6 +33,11 @@ class StoreViewTest extends \PHPUnit\Framework\TestCase */ protected $indexerRegistryMock; + /** + * @var \Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer|\PHPUnit_Framework_MockObject_MockObject + */ + protected $tableMaintainer; + /** * @var \PHPUnit_Framework_MockObject_MockObject */ @@ -51,15 +56,30 @@ protected function setUp() ); $this->subject = $this->createMock(Group::class); $this->indexerRegistryMock = $this->createPartialMock(IndexerRegistry::class, ['get']); - $this->storeMock = $this->createPartialMock(Store::class, ['isObjectNew', 'dataHasChangedFor', '__wakeup']); + $this->storeMock = $this->createPartialMock( + Store::class, + [ + 'isObjectNew', + 'getId', + 'dataHasChangedFor', + '__wakeup' + ] + ); + $this->tableMaintainer = $this->createPartialMock( + \Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer::class, + [ + 'createTablesForStore' + ] + ); - $this->model = new StoreView($this->indexerRegistryMock); + $this->model = new StoreView($this->indexerRegistryMock, $this->tableMaintainer); } public function testAroundSaveNewObject() { $this->mockIndexerMethods(); - $this->storeMock->expects($this->once())->method('isObjectNew')->willReturn(true); + $this->storeMock->expects($this->atLeastOnce())->method('isObjectNew')->willReturn(true); + $this->storeMock->expects($this->atLeastOnce())->method('getId')->willReturn(1); $this->model->beforeSave($this->subject, $this->storeMock); $this->assertSame($this->subject, $this->model->afterSave($this->subject, $this->subject, $this->storeMock)); } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php index aec6549f400fc..683a96133ad30 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php @@ -182,6 +182,11 @@ private function customizeAddAttributeModal(array $meta) . '.create_new_attribute_modal', 'actionName' => 'toggleModal', ], + [ + 'targetName' => 'product_form.product_form.add_attribute_modal' + . '.create_new_attribute_modal.product_attribute_add_form', + 'actionName' => 'destroyInserted' + ], [ 'targetName' => 'product_form.product_form.add_attribute_modal' diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml index 10251d35dffcd..ca8390a6c8f8a 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/di.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml @@ -220,4 +220,7 @@ Magento\Catalog\Ui\DataProvider\Product\AddSearchKeyConditionToCollection + + + diff --git a/app/code/Magento/Catalog/etc/frontend/di.xml b/app/code/Magento/Catalog/etc/frontend/di.xml index 3989a62a56cc4..659ba2b731366 100644 --- a/app/code/Magento/Catalog/etc/frontend/di.xml +++ b/app/code/Magento/Catalog/etc/frontend/di.xml @@ -100,4 +100,7 @@ + + + diff --git a/app/code/Magento/Catalog/etc/webapi_rest/di.xml b/app/code/Magento/Catalog/etc/webapi_rest/di.xml index 1d2b013f2035d..a0d3e850b3c64 100644 --- a/app/code/Magento/Catalog/etc/webapi_rest/di.xml +++ b/app/code/Magento/Catalog/etc/webapi_rest/di.xml @@ -8,7 +8,6 @@ - @@ -16,4 +15,7 @@ + + + diff --git a/app/code/Magento/Catalog/etc/webapi_soap/di.xml b/app/code/Magento/Catalog/etc/webapi_soap/di.xml index 98a8ef4de8408..a0d3e850b3c64 100644 --- a/app/code/Magento/Catalog/etc/webapi_soap/di.xml +++ b/app/code/Magento/Catalog/etc/webapi_soap/di.xml @@ -15,4 +15,7 @@ + + + diff --git a/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js b/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js index 5ffc587f65bec..9053c700d1a3b 100644 --- a/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js +++ b/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js @@ -6,12 +6,13 @@ var config = { map: { '*': { - categoryForm: 'Magento_Catalog/catalog/category/form', - newCategoryDialog: 'Magento_Catalog/js/new-category-dialog', - categoryTree: 'Magento_Catalog/js/category-tree', - productGallery: 'Magento_Catalog/js/product-gallery', - baseImage: 'Magento_Catalog/catalog/base-image-uploader', - productAttributes: 'Magento_Catalog/catalog/product-attributes' + categoryForm: 'Magento_Catalog/catalog/category/form', + newCategoryDialog: 'Magento_Catalog/js/new-category-dialog', + categoryTree: 'Magento_Catalog/js/category-tree', + productGallery: 'Magento_Catalog/js/product-gallery', + baseImage: 'Magento_Catalog/catalog/base-image-uploader', + productAttributes: 'Magento_Catalog/catalog/product-attributes', + categoryCheckboxTree: 'Magento_Catalog/js/category-checkbox-tree' } }, deps: [ diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml index 740d389735974..00a1580923a7b 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml @@ -5,187 +5,33 @@ */ // @codingStandardsIgnoreFile - -?> - - - - - - categoryLoader.buildHash = function(node) + diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml index 44e13da69fc3b..6c5d37a92ea4a 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml @@ -152,7 +152,6 @@ true true container - attribute_options.position @@ -189,12 +188,8 @@ - - true - text false - position diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js b/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js new file mode 100644 index 0000000000000..bc44128663cd0 --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js @@ -0,0 +1,271 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/* global Ext, varienWindowOnload, varienElementMethods */ + +define([ + 'jquery', + 'prototype', + 'extjs/ext-tree-checkbox', + 'mage/adminhtml/form' +], function (jQuery) { + 'use strict'; + + return function (config) { + var tree, + options = { + dataUrl: config.dataUrl, + divId: config.divId, + rootVisible: config.rootVisible, + useAjax: config.useAjax, + currentNodeId: config.currentNodeId, + jsFormObject: window[config.jsFormObject], + name: config.name, + checked: config.checked, + allowDrop: config.allowDrop, + rootId: config.rootId, + expanded: config.expanded, + categoryId: config.categoryId, + treeJson: config.treeJson + }, + data = {}, + parameters = {}, + root = {}, + len = 0, + key = ''; + + /** + * Fix ext compatibility with prototype 1.6 + */ + Ext.lib.Event.getTarget = function (e) { + var ee = e.browserEvent || e; + + return ee.target ? Event.element(ee) : null; + }; + + /** + * @param {Object} el + * @param {Object} nodeConfig + */ + Ext.tree.TreePanel.Enhanced = function (el, nodeConfig) { + Ext.tree.TreePanel.Enhanced.superclass.constructor.call(this, el, nodeConfig); + }; + + Ext.extend(Ext.tree.TreePanel.Enhanced, Ext.tree.TreePanel, { + /** + * @param {Object} treeConfig + * @param {Boolean} firstLoad + */ + loadTree: function (treeConfig, firstLoad) { + parameters = treeConfig.parameters, + data = treeConfig.data, + root = new Ext.tree.TreeNode(parameters); + + if (typeof parameters.rootVisible !== 'undefined') { + this.rootVisible = parameters.rootVisible * 1; + } + + this.nodeHash = {}; + this.setRootNode(root); + + if (firstLoad) { + this.addListener('click', this.categoryClick.createDelegate(this)); + } + + this.loader.buildCategoryTree(root, data); + this.el.dom.innerHTML = ''; + // render the tree + this.render(); + }, + + /** + * @param {Object} node + */ + categoryClick: function (node) { + node.getUI().check(!node.getUI().checked()); + } + }); + + jQuery(function () { + var categoryLoader = new Ext.tree.TreeLoader({ + dataUrl: config.dataUrl + }); + + /** + * @param {Object} response + * @param {Object} parent + * @param {Function} callback + */ + categoryLoader.processResponse = function (response, parent, callback) { + config = JSON.parse(response.responseText); + + this.buildCategoryTree(parent, config); + + if (typeof callback === 'function') { + callback(this, parent); + } + }; + + /** + * @param {Object} nodeConfig + * @returns {Object} + */ + categoryLoader.createNode = function (nodeConfig) { + var node; + + nodeConfig.uiProvider = Ext.tree.CheckboxNodeUI; + + if (nodeConfig.children && !nodeConfig.children.length) { + delete nodeConfig.children; + node = new Ext.tree.AsyncTreeNode(nodeConfig); + } else { + node = new Ext.tree.TreeNode(nodeConfig); + } + + return node; + }; + + /** + * @param {Object} parent + * @param {Object} nodeConfig + * @param {Integer} i + */ + categoryLoader.processCategoryTree = function (parent, nodeConfig, i) { + var node, + _node = {}; + + nodeConfig[i].uiProvider = Ext.tree.CheckboxNodeUI; + + _node = Object.clone(nodeConfig[i]); + + if (_node.children && !_node.children.length) { + delete _node.children; + node = new Ext.tree.AsyncTreeNode(_node); + } else { + node = new Ext.tree.TreeNode(nodeConfig[i]); + } + parent.appendChild(node); + node.loader = node.getOwnerTree().loader; + + if (_node.children) { + categoryLoader.buildCategoryTree(node, _node.children); + } + }; + + /** + * @param {Object} parent + * @param {Object} nodeConfig + * @returns {void} + */ + categoryLoader.buildCategoryTree = function (parent, nodeConfig) { + var j = 0; + + if (!nodeConfig) { + return null; + } + + if (parent && nodeConfig && nodeConfig.length) { + for (j = 0; j < nodeConfig.length; j++) { + categoryLoader.processCategoryTree(parent, nodeConfig, j); + } + } + }; + + /** + * + * @param {Object} hash + * @param {Object} node + * @returns {Object} + */ + categoryLoader.buildHashChildren = function (hash, node) { + var j = 0; + + if (node.childNodes.length > 0 || node.loaded === false && node.loading === false) { + hash.children = []; + + for (j = 0, len = node.childNodes.length; j < len; j++) { + hash.children = hash.children ? hash.children : []; + hash.children.push(this.buildHash(node.childNodes[j])); + } + } + + return hash; + }; + + /** + * @param {Object} node + * @returns {Object} + */ + categoryLoader.buildHash = function (node) { + var hash = {}; + + hash = this.toArray(node.attributes); + + return categoryLoader.buildHashChildren(hash, node); + }; + + /** + * @param {Object} attributes + * @returns {Object} + */ + categoryLoader.toArray = function (attributes) { + data = {}; + + for (key in attributes) { + + if (attributes[key]) { + data[key] = attributes[key]; + } + } + + return data; + }; + + categoryLoader.on('beforeload', function (treeLoader, node) { + treeLoader.baseParams.id = node.attributes.id; + }); + + categoryLoader.on('load', function () { + varienWindowOnload(); + }); + + tree = new Ext.tree.TreePanel.Enhanced(options.divId, { + animate: false, + loader: categoryLoader, + enableDD: false, + containerScroll: true, + selModel: new Ext.tree.CheckNodeMultiSelectionModel(), + rootVisible: options.rootVisible, + useAjax: options.useAjax, + currentNodeId: options.currentNodeId, + addNodeTo: false, + rootUIProvider: Ext.tree.CheckboxNodeUI + }); + + tree.on('check', function (node) { + options.jsFormObject.updateElement.value = this.getChecked().join(', '); + varienElementMethods.setHasChanges(node.getUI().checkbox); + }, tree); + + // set the root node + //jscs:disable requireCamelCaseOrUpperCaseIdentifiers + parameters = { + text: options.name, + draggable: false, + checked: options.checked, + uiProvider: Ext.tree.CheckboxNodeUI, + allowDrop: options.allowDrop, + id: options.rootId, + expanded: options.expanded, + category_id: options.categoryId + }; + //jscs:enable requireCamelCaseOrUpperCaseIdentifiers + + tree.loadTree({ + parameters: parameters, data: options.treeJson + }, true); + }); + }; +}); diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js index 51ffeaea0fc0c..2f6703cc92eac 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js @@ -54,9 +54,16 @@ define([ if (!_.isEmpty(this.suffixName) || _.isNumber(this.suffixName)) { suffixName = '.' + this.suffixName; } - this.dataScope = 'data.' + this.prefixName + '.' + this.elementName + suffixName; - this.links.value = this.provider + ':' + this.dataScope; + this.exportDataLink = 'data.' + this.prefixName + '.' + this.elementName + suffixName; + this.exports.value = this.provider + ':' + this.exportDataLink; + }, + + /** @inheritdoc */ + destroy: function () { + this._super(); + + this.source.remove(this.exportDataLink); }, /** diff --git a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html index d4cfb02611416..9a52dcefa3042 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html +++ b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html @@ -7,7 +7,7 @@ escapeHtml($viewModel->getCategoryUrlSuffix()); ?>", "useCategoryPathInUrl": = (int)$viewModel->isCategoryUsedInProductUrl(); ?>, - "product": "= $block->escapeHtml($viewModel->getProductName()); ?>" + "product": "= $block->escapeHtml($block->escapeJsQuote($viewModel->getProductName(), '"')); ?>" } }'> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/compare/list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/compare/list.phtml index 6a2dd1f27d4a9..f1d4b32031e49 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/compare/list.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/compare/list.phtml @@ -10,7 +10,7 @@ ?> getItems()->getSize() ?> - + = /* @escapeNotVerified */ __('Print This Page') ?> @@ -29,7 +29,7 @@ = /* @escapeNotVerified */ __('Remove Product') ?> - + helper('Magento\Catalog\Helper\Product\Compare');?> @@ -59,7 +59,7 @@ = $block->getReviewsSummaryHtml($_item, 'short') ?> = /* @escapeNotVerified */ $block->getProductPrice($_item, '-compare-list-top') ?> - + isSaleable()): ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml index 5a064b33355a4..1bfa30478df8a 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml @@ -47,21 +47,11 @@ "data": = /* @escapeNotVerified */ $block->getGalleryImagesJson() ?>, "options": { "nav": "= /* @escapeNotVerified */ $block->getVar("gallery/nav") ?>", - getVar("gallery/loop"))): ?> - "loop": = /* @escapeNotVerified */ $block->getVar("gallery/loop") ?>, - - getVar("gallery/keyboard"))): ?> - "keyboard": = /* @escapeNotVerified */ $block->getVar("gallery/keyboard") ?>, - - getVar("gallery/arrows"))): ?> - "arrows": = /* @escapeNotVerified */ $block->getVar("gallery/arrows") ?>, - - getVar("gallery/allowfullscreen"))): ?> - "allowfullscreen": = /* @escapeNotVerified */ $block->getVar("gallery/allowfullscreen") ?>, - - getVar("gallery/caption"))): ?> - "showCaption": = /* @escapeNotVerified */ $block->getVar("gallery/caption") ?>, - + "loop": = /* @escapeNotVerified */ $block->getVar("gallery/loop") ? 'true' : 'false' ?>, + "keyboard": = /* @escapeNotVerified */ $block->getVar("gallery/keyboard") ? 'true' : 'false' ?>, + "arrows": = /* @escapeNotVerified */ $block->getVar("gallery/arrows") ? 'true' : 'false' ?>, + "allowfullscreen": = /* @escapeNotVerified */ $block->getVar("gallery/allowfullscreen") ? 'true' : 'false' ?>, + "showCaption": = /* @escapeNotVerified */ $block->getVar("gallery/caption") ? 'true' : 'false' ?>, "width": "= /* @escapeNotVerified */ $block->getImageAttribute('product_page_image_medium', 'width') ?>", "thumbwidth": "= /* @escapeNotVerified */ $block->getImageAttribute('product_page_image_small', 'width') ?>", getImageAttribute('product_page_image_small', 'height') || $block->getImageAttribute('product_page_image_small', 'width')): ?> @@ -76,28 +66,18 @@ "transitionduration": = /* @escapeNotVerified */ $block->getVar("gallery/transition/duration") ?>, "transition": "= /* @escapeNotVerified */ $block->getVar("gallery/transition/effect") ?>", - getVar("gallery/navarrows"))): ?> - "navarrows": = /* @escapeNotVerified */ $block->getVar("gallery/navarrows") ?>, - + "navarrows": = /* @escapeNotVerified */ $block->getVar("gallery/navarrows") ? 'true' : 'false' ?>, "navtype": "= /* @escapeNotVerified */ $block->getVar("gallery/navtype") ?>", "navdir": "= /* @escapeNotVerified */ $block->getVar("gallery/navdir") ?>" }, "fullscreen": { "nav": "= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/nav") ?>", - getVar("gallery/fullscreen/loop")): ?> - "loop": = /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/loop") ?>, - + "loop": = /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/loop") ? 'true' : 'false' ?>, "navdir": "= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/navdir") ?>", - getVar("gallery/transition/navarrows")): ?> - "navarrows": = /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/navarrows") ?>, - + "navarrows": = /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/navarrows") ? 'true' : 'false' ?>, "navtype": "= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/navtype") ?>", - getVar("gallery/fullscreen/arrows")): ?> - "arrows": = /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/arrows") ?>, - - getVar("gallery/fullscreen/caption")): ?> - "showCaption": = /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/caption") ?>, - + "arrows": = /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/arrows") ? 'true' : 'false' ?>, + "showCaption": = /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/caption") ? 'true' : 'false' ?>, getVar("gallery/fullscreen/transition/duration")): ?> "transitionduration": = /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/transition/duration") ?>, diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php b/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php new file mode 100644 index 0000000000000..baa456c7821ed --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php @@ -0,0 +1,34 @@ +selectionSet->selections ?? []; + $depth = count($selections) ? 1 : 0; + $childrenDepth = [0]; + foreach ($selections as $node) { + $childrenDepth[] = $this->calculate($node); + } + + return $depth + max($childrenDepth); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php b/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php new file mode 100644 index 0000000000000..da4ec37c51da4 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php @@ -0,0 +1,56 @@ +flattener = $flattener; + $this->dataObjectProcessor = $dataObjectProcessor; + } + + /** + * Hydrate and flatten category object to flat array + * + * @param CategoryInterface $category + * @return array + */ + public function hydrateCategory(CategoryInterface $category) : array + { + $categoryData = $this->dataObjectProcessor->buildOutputDataArray($category, CategoryInterface::class); + $categoryData['id'] = $category->getId(); + $categoryData['product_count'] = $category->getProductCount(); + $categoryData['children'] = []; + $categoryData['available_sort_by'] = $category->getAvailableSortBy(); + return $this->flattener->flatten($categoryData); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php b/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php new file mode 100644 index 0000000000000..eb57873850b80 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php @@ -0,0 +1,44 @@ +resourceConnection = $resourceConnection; + $this->resourceCategory = $resourceCategory; + } + + /** + * Calculate level data for root category ID specified in GraphQL request + * + * @param int $rootCategoryId + * @return int + */ + public function calculate(int $rootCategoryId) : int + { + $connection = $this->resourceConnection->getConnection(); + $select = $connection->select() + ->from($connection->getTableName('catalog_category_entity'), 'level') + ->where($this->resourceCategory->getLinkField() . " = ?", $rootCategoryId); + return (int) $connection->fetchOne($select); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/CategoryAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/CategoryAttributeReader.php index 0b1e76313b46d..0ca72d9ff9519 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Config/CategoryAttributeReader.php +++ b/app/code/Magento/CatalogGraphQl/Model/Config/CategoryAttributeReader.php @@ -30,7 +30,15 @@ class CategoryAttributeReader implements ReaderInterface 'is_active', 'children', 'level', - 'default_sort_by' + 'default_sort_by', + 'all_children', + 'page_layout', + 'custom_design', + 'custom_design_from', + 'custom_design_to', + 'custom_layout_update', + 'custom_use_parent_settings', + 'custom_apply_to_products', ]; /** diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php index 52ecdccecdc3f..a17de7374534b 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php @@ -110,7 +110,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value CategoryInterface::class ); $categories[$item->getId()] = $this->customAttributesFlattener - ->flaternize($categories[$item->getId()]); + ->flatten($categories[$item->getId()]); $categories[$item->getId()]['product_count'] = $item->getProductCount(); } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index c78237b7bcd45..f631e5ff61d2e 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -55,11 +55,11 @@ public function __construct( */ private function assertFiltersAreValidAndGetCategoryRootIds(array $args) : int { - if (!isset($args['filter']['root_category_id'])) { - throw new GraphQlInputException(__('"root_category_id" filter should be specified')); + if (!isset($args['id'])) { + throw new GraphQlInputException(__('"id for category should be specified')); } - return (int) $args['filter']['root_category_id']; + return (int) $args['id']; } /** @@ -74,9 +74,11 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $rootCategoryId = $this->assertFiltersAreValidAndGetCategoryRootIds($args); $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId); - return [ - 'category_tree' => reset($categoriesTree) - ]; + if (!empty($categoriesTree)) { + return current($categoriesTree); + } else { + return null; + } }); } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index 6d3bb35d654e2..3c01579410638 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -8,14 +8,15 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider; use GraphQL\Language\AST\FieldNode; +use Magento\CatalogGraphQl\Model\Category\DepthCalculator; +use Magento\CatalogGraphQl\Model\Category\Hydrator; +use Magento\CatalogGraphQl\Model\Category\LevelCalculator; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Catalog\Api\Data\CategoryInterface; -use Magento\Catalog\Model\ResourceModel\Category; use Magento\Catalog\Model\ResourceModel\Category\Collection; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; use Magento\CatalogGraphQl\Model\AttributesJoiner; -use Magento\Framework\App\ResourceConnection; -use Magento\Framework\Reflection\DataObjectProcessor; /** * Category tree data provider @@ -38,47 +39,47 @@ class CategoryTree private $attributesJoiner; /** - * @var ResourceConnection + * @var DepthCalculator */ - private $resourceConnection; + private $depthCalculator; /** - * @var Category + * @var LevelCalculator */ - private $resourceCategory; + private $levelCalculator; /** - * @var CustomAttributesFlattener + * @var MetadataPool */ - private $customAttributesFlattener; + private $metadata; /** - * @var DataObjectProcessor + * @var Hydrator */ - private $dataObjectProcessor; + private $hydrator; /** * @param CollectionFactory $collectionFactory * @param AttributesJoiner $attributesJoiner - * @param ResourceConnection $resourceConnection - * @param Category $resourceCategory - * @param CustomAttributesFlattener $customAttributesFlattener - * @param DataObjectProcessor $dataObjectProcessor + * @param DepthCalculator $depthCalculator + * @param LevelCalculator $levelCalculator + * @param MetadataPool $metadata + * @param Hydrator $hydrator */ public function __construct( CollectionFactory $collectionFactory, AttributesJoiner $attributesJoiner, - ResourceConnection $resourceConnection, - Category $resourceCategory, - CustomAttributesFlattener $customAttributesFlattener, - DataObjectProcessor $dataObjectProcessor + DepthCalculator $depthCalculator, + LevelCalculator $levelCalculator, + MetadataPool $metadata, + Hydrator $hydrator ) { $this->collectionFactory = $collectionFactory; $this->attributesJoiner = $attributesJoiner; - $this->resourceConnection = $resourceConnection; - $this->resourceCategory = $resourceCategory; - $this->customAttributesFlattener = $customAttributesFlattener; - $this->dataObjectProcessor = $dataObjectProcessor; + $this->depthCalculator = $depthCalculator; + $this->levelCalculator = $levelCalculator; + $this->metadata = $metadata; + $this->hydrator = $hydrator; } /** @@ -91,13 +92,17 @@ public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId) : array $categoryQuery = $resolveInfo->fieldASTs[0]; $collection = $this->collectionFactory->create(); $this->joinAttributesRecursively($collection, $categoryQuery); - $depth = $this->calculateDepth($categoryQuery); - $level = $this->getLevelByRootCategoryId($rootCategoryId); + $depth = $this->depthCalculator->calculate($categoryQuery); + $level = $this->levelCalculator->calculate($rootCategoryId); //Search for desired part of category tree + $collection->addPathFilter(sprintf('.*/%s/[/0-9]*$', $rootCategoryId)); $collection->addFieldToFilter('level', ['gt' => $level]); $collection->addFieldToFilter('level', ['lteq' => $level + $depth - self::DEPTH_OFFSET]); $collection->setOrder('level'); - $collection->getSelect()->orWhere($this->resourceCategory->getLinkField() . ' = ?', $rootCategoryId); + $collection->getSelect()->orWhere( + $this->metadata->getMetadata(CategoryInterface::class)->getLinkField() . ' = ?', + $rootCategoryId + ); return $this->processTree($collection->getIterator()); } @@ -113,7 +118,7 @@ private function processTree(\Iterator $iterator) : array $category = $iterator->current(); $iterator->next(); $nextCategory = $iterator->current(); - $tree[$category->getId()] = $this->hydrateCategory($category); + $tree[$category->getId()] = $this->hydrator->hydrateCategory($category); if ($nextCategory && (int) $nextCategory->getLevel() !== (int) $category->getLevel()) { $tree[$category->getId()]['children'] = $this->processTree($iterator); } @@ -122,36 +127,6 @@ private function processTree(\Iterator $iterator) : array return $tree; } - /** - * Hydrate and flatten category object to flat array - * - * @param CategoryInterface $category - * @return array - */ - private function hydrateCategory(CategoryInterface $category) : array - { - $categoryData = $this->dataObjectProcessor->buildOutputDataArray($category, CategoryInterface::class); - $categoryData['id'] = $category->getId(); - $categoryData['product_count'] = $category->getProductCount(); - $categoryData['all_children'] = $category->getAllChildren(); - $categoryData['children'] = []; - $categoryData['available_sort_by'] = $category->getAvailableSortBy(); - return $this->customAttributesFlattener->flaternize($categoryData); - } - - /** - * @param int $rootCategoryId - * @return int - */ - private function getLevelByRootCategoryId(int $rootCategoryId) : int - { - $connection = $this->resourceConnection->getConnection(); - $select = $connection->select() - ->from($connection->getTableName('catalog_category_entity'), 'level') - ->where($this->resourceCategory->getLinkField() . " = ?", $rootCategoryId); - return (int) $connection->fetchOne($select); - } - /** * @param Collection $collection * @param FieldNode $fieldNode @@ -171,20 +146,4 @@ private function joinAttributesRecursively(Collection $collection, FieldNode $fi $this->joinAttributesRecursively($collection, $node); } } - - /** - * @param FieldNode $fieldNode - * @return int - */ - private function calculateDepth(FieldNode $fieldNode) : int - { - $selections = $fieldNode->selectionSet->selections ?? []; - $depth = count($selections) ? 1 : 0; - $childrenDepth = [0]; - foreach ($selections as $node) { - $childrenDepth[] = $this->calculateDepth($node); - } - - return $depth + max($childrenDepth); - } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CustomAttributesFlattener.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CustomAttributesFlattener.php index e5dfe760372ff..3f7af2610db1f 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CustomAttributesFlattener.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CustomAttributesFlattener.php @@ -13,12 +13,12 @@ class CustomAttributesFlattener { /** - * Graphql is waiting for flat array + * Flatten custom attributes within its enclosing array to normalize key-value pairs. * * @param array $categoryData * @return array */ - public function flaternize(array $categoryData) : array + public function flatten(array $categoryData) : array { if (!isset($categoryData['custom_attributes'])) { return $categoryData; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php index 99b892e868d2c..96bef3ffc09c4 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php @@ -33,7 +33,7 @@ class ProductEntityAttributesForAst implements FieldEntityAttributesInterface */ public function __construct( ConfigInterface $config, - array $additionalAttributes = ['min_price', 'max_price'] + array $additionalAttributes = ['min_price', 'max_price', 'category_ids'] ) { $this->config = $config; $this->additionalAttributes = $additionalAttributes; diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index f223b1c9eec07..ca1ff78654319 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -10,9 +10,9 @@ type Query { sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") ): Products @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Products") @doc(description: "The products query searches for products that match the criteria specified in the search and filter attributes") - categories ( - filter: CategoryFilterInput @doc(description: "Filter for categories") - ): Categories + category ( + id: Int @doc(description: "Id of the category") + ): CategoryTree @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") } @@ -263,9 +263,6 @@ interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\ new_from_date: String @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo") new_to_date: String @doc(description: "The end date for new product listings") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo") tier_price: Float @doc(description: "The price when tier pricing is in effect and the items purchased threshold has been reached") - custom_layout_update: String @doc(description: "XML code that is applied as a layout update to the product page") - custom_layout: String @doc(description: "The name of a custom layout") - category_ids: [Int] @doc(description: "An array of category IDs the product belongs to") options_container: String @doc(description: "If the product has multiple options, determines where they appear on the product page") image_label: String @doc(description: "The label assigned to a product image") small_image_label: String @doc(description: "The label assigned to a product's small image") @@ -300,11 +297,7 @@ type CustomizableAreaValue @doc(description: "CustomizableAreaValue defines the max_characters: Int @doc(description: "The maximum number of characters that can be entered for this customizable option") } -type Categories @doc(description: "Categories aggregates the category tree of a product") { - category_tree: CategoryTree @doc(description: "Tree of categories") -} - -type CategoryTree implements CategoryInterface @doc(description: "Category Tree") { +type CategoryTree implements CategoryInterface @doc(description: "Category Tree implementation") { children: [CategoryTree] @doc(description: "Child categories tree") @resolve(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") } @@ -376,7 +369,6 @@ interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model path_in_store: String @doc(description: "Category path in store") url_key: String @doc(description: "The url key assigned to the category") url_path: String @doc(description: "The url path assigned to the category") - is_active: Boolean @doc(description: "Indicates whether the category is enabled") position: Int @doc(description: "The position of the category relative to other categories at the same level in tree") level: Int @doc(description: "Indicates the depth of the category within the tree") created_at: String @doc(description: "Timestamp indicating when the category was created") @@ -412,10 +404,6 @@ type Products @doc(description: "The Products object is the top-level object ret filters: [LayerFilter] @doc(description: "Layered navigation filters array") } -input CategoryFilterInput @doc(description: "Identifies which category attributes to search for and return.") { - root_category_id: Int @doc(description: "Id of the root category in the category tree to use as the starting point of your search.") -} - input ProductFilterInput @doc(description: "ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { name: FilterTypeInput @doc(description: "The product name. Customers use this name to identify the product.") sku: FilterTypeInput @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer") diff --git a/app/code/Magento/CatalogInventory/i18n/en_US.csv b/app/code/Magento/CatalogInventory/i18n/en_US.csv index 93406163cbe1b..19b73f847b46d 100644 --- a/app/code/Magento/CatalogInventory/i18n/en_US.csv +++ b/app/code/Magento/CatalogInventory/i18n/en_US.csv @@ -55,11 +55,7 @@ Inventory,Inventory "Only X left Threshold","Only X left Threshold" "Display Products Availability in Stock on Storefront","Display Products Availability in Stock on Storefront" "Product Stock Options","Product Stock Options" -" - Please note that these settings apply to individual items in the cart, not to the entire cart. - "," - Please note that these settings apply to individual items in the cart, not to the entire cart. - " +"Please note that these settings apply to individual items in the cart, not to the entire cart.","Please note that these settings apply to individual items in the cart, not to the entire cart." "Manage Stock","Manage Stock" Backorders,Backorders "Maximum Qty Allowed in Shopping Cart","Maximum Qty Allowed in Shopping Cart" diff --git a/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Widget/Chooser/Sku.php b/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Widget/Chooser/Sku.php index 333ee845798ec..306d3b9a347b4 100644 --- a/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Widget/Chooser/Sku.php +++ b/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Widget/Chooser/Sku.php @@ -207,7 +207,7 @@ protected function _prepareColumns() public function getGridUrl() { return $this->getUrl( - 'catalog_rule/*/chooser', + '*/*/chooser', ['_current' => true, 'current_grid_id' => $this->getId(), 'collapse' => null] ); } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php b/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php index f2dd8968a903d..1f62200fc6b1b 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php @@ -12,6 +12,7 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\CatalogRule\Model\Indexer\IndexBuilder\ProductLoader; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; /** * @api @@ -136,6 +137,11 @@ class IndexBuilder */ private $activeTableSwitcher; + /** + * @var TableSwapper + */ + private $tableSwapper; + /** * @var ProductLoader */ @@ -160,6 +166,7 @@ class IndexBuilder * @param RuleProductPricesPersistor|null $pricesPersistor * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|null $activeTableSwitcher * @param ProductLoader|null $productLoader + * @param TableSwapper|null $tableSwapper * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -180,7 +187,8 @@ public function __construct( ReindexRuleProductPrice $reindexRuleProductPrice = null, RuleProductPricesPersistor $pricesPersistor = null, \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null, - ProductLoader $productLoader = null + ProductLoader $productLoader = null, + TableSwapper $tableSwapper = null ) { $this->resource = $resource; $this->connection = $resource->getConnection(); @@ -218,6 +226,8 @@ public function __construct( $this->productLoader = $productLoader ?? ObjectManager::getInstance()->get( ProductLoader::class ); + $this->tableSwapper = $tableSwapper ?? + ObjectManager::getInstance()->get(TableSwapper::class); } /** @@ -296,13 +306,6 @@ public function reindexFull() */ protected function doReindexFull() { - $this->connection->truncateTable( - $this->getTable($this->activeTableSwitcher->getAdditionalTableName('catalogrule_product')) - ); - $this->connection->truncateTable( - $this->getTable($this->activeTableSwitcher->getAdditionalTableName('catalogrule_product_price')) - ); - foreach ($this->getAllRules() as $rule) { $this->reindexRuleProduct->execute($rule, $this->batchCount, true); } @@ -310,8 +313,7 @@ protected function doReindexFull() $this->reindexRuleProductPrice->execute($this->batchCount, null, true); $this->reindexRuleGroupWebsite->execute(true); - $this->activeTableSwitcher->switchTable( - $this->connection, + $this->tableSwapper->swapIndexTables( [ $this->getTable('catalogrule_product'), $this->getTable('catalogrule_product_price'), diff --git a/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapper.php b/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapper.php new file mode 100644 index 0000000000000..f99f8c50a7f9a --- /dev/null +++ b/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapper.php @@ -0,0 +1,125 @@ +resourceConnection = $resource; + } + + /** + * Create temporary table based on given table to use instead of original. + * + * @param string $originalTableName + * + * @return string Created table name. + */ + private function createTemporaryTable(string $originalTableName): string + { + $temporaryTableName = $this->resourceConnection->getTableName( + $originalTableName . '__temp' . $this->generateRandomSuffix() + ); + + $this->resourceConnection->getConnection()->query( + sprintf( + 'create table %s like %s', + $temporaryTableName, + $this->resourceConnection->getTableName($originalTableName) + ) + ); + + return $temporaryTableName; + } + + /** + * Random suffix for temporary tables not to conflict with each other. + * + * @return string + */ + private function generateRandomSuffix(): string + { + return bin2hex(random_bytes(4)); + } + + /** + * @inheritDoc + */ + public function getWorkingTableName(string $originalTable): string + { + $originalTable = $this->resourceConnection->getTableName($originalTable); + if (!array_key_exists($originalTable, $this->temporaryTables)) { + $this->temporaryTables[$originalTable] = $this->createTemporaryTable($originalTable); + } + + return $this->temporaryTables[$originalTable]; + } + + /** + * @inheritDoc + */ + public function swapIndexTables(array $originalTablesNames) + { + $toRename = []; + /** @var string[] $toDrop */ + $toDrop = []; + /** @var string[] $temporaryTablesRenamed */ + $temporaryTablesRenamed = []; + //Renaming temporary tables to original tables' names, dropping old + //tables. + foreach ($originalTablesNames as $tableName) { + $tableName = $this->resourceConnection->getTableName($tableName); + $temporaryOriginalName = $this->resourceConnection->getTableName( + $tableName . $this->generateRandomSuffix() + ); + $temporaryTableName = $this->getWorkingTableName($tableName); + $toRename[] = [ + 'oldName' => $tableName, + 'newName' => $temporaryOriginalName, + ]; + $toRename[] = [ + 'oldName' => $temporaryTableName, + 'newName' => $tableName, + ]; + $toDrop[] = $temporaryOriginalName; + $temporaryTablesRenamed[] = $tableName; + } + + //Swapping tables. + $this->resourceConnection->getConnection()->renameTablesBatch($toRename); + //Cleaning up. + foreach ($temporaryTablesRenamed as $tableName) { + unset($this->temporaryTables[$tableName]); + } + //Removing old ones. + foreach ($toDrop as $tableName) { + $this->resourceConnection->getConnection()->dropTable($tableName); + } + } +} diff --git a/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapperInterface.php b/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapperInterface.php new file mode 100644 index 0000000000000..2f37e680949ae --- /dev/null +++ b/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapperInterface.php @@ -0,0 +1,33 @@ +dateTime = $dateTime; $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; + $this->tableSwapper = $tableSwapper ?? + ObjectManager::getInstance()->get(TableSwapper::class); } /** @@ -61,10 +74,10 @@ public function execute($useAdditionalTable = false) $ruleProductTable = $this->resource->getTableName('catalogrule_product'); if ($useAdditionalTable) { $indexTable = $this->resource->getTableName( - $this->activeTableSwitcher->getAdditionalTableName('catalogrule_group_website') + $this->tableSwapper->getWorkingTableName('catalogrule_group_website') ); $ruleProductTable = $this->resource->getTableName( - $this->activeTableSwitcher->getAdditionalTableName('catalogrule_product') + $this->tableSwapper->getWorkingTableName('catalogrule_product') ); } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php index 534061d593123..55a234bb8ae27 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php @@ -6,6 +6,10 @@ namespace Magento\CatalogRule\Model\Indexer; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\Framework\App\ObjectManager; + /** * Reindex rule relations with products. */ @@ -17,20 +21,29 @@ class ReindexRuleProduct private $resource; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher + * @var ActiveTableSwitcher */ private $activeTableSwitcher; + /** + * @var TableSwapper + */ + private $tableSwapper; + /** * @param \Magento\Framework\App\ResourceConnection $resource - * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + * @param ActiveTableSwitcher $activeTableSwitcher + * @param TableSwapper|null $tableSwapper */ public function __construct( \Magento\Framework\App\ResourceConnection $resource, - \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + ActiveTableSwitcher $activeTableSwitcher, + TableSwapper $tableSwapper = null ) { $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; + $this->tableSwapper = $tableSwapper ?? + ObjectManager::getInstance()->get(TableSwapper::class); } /** @@ -65,7 +78,7 @@ public function execute( $indexTable = $this->resource->getTableName('catalogrule_product'); if ($useAdditionalTable) { $indexTable = $this->resource->getTableName( - $this->activeTableSwitcher->getAdditionalTableName('catalogrule_product') + $this->tableSwapper->getWorkingTableName('catalogrule_product') ); } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php b/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php index 853be1888b5b9..0b1264a216257 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php @@ -6,6 +6,10 @@ namespace Magento\CatalogRule\Model\Indexer; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\Framework\App\ObjectManager; + /** * Persist product prices to index table. */ @@ -22,23 +26,32 @@ class RuleProductPricesPersistor private $dateFormat; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher + * @var ActiveTableSwitcher */ private $activeTableSwitcher; + /** + * @var TableSwapper + */ + private $tableSwapper; + /** * @param \Magento\Framework\Stdlib\DateTime $dateFormat * @param \Magento\Framework\App\ResourceConnection $resource - * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + * @param ActiveTableSwitcher $activeTableSwitcher + * @param TableSwapper|null $tableSwapper */ public function __construct( \Magento\Framework\Stdlib\DateTime $dateFormat, \Magento\Framework\App\ResourceConnection $resource, - \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + ActiveTableSwitcher $activeTableSwitcher, + TableSwapper $tableSwapper = null ) { $this->dateFormat = $dateFormat; $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; + $this->tableSwapper = $tableSwapper ?? + ObjectManager::getInstance()->get(TableSwapper::class); } /** @@ -59,7 +72,7 @@ public function execute(array $priceData, $useAdditionalTable = false) $indexTable = $this->resource->getTableName('catalogrule_product_price'); if ($useAdditionalTable) { $indexTable = $this->resource->getTableName( - $this->activeTableSwitcher->getAdditionalTableName('catalogrule_product_price') + $this->tableSwapper->getWorkingTableName('catalogrule_product_price') ); } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/RuleProductsSelectBuilder.php b/app/code/Magento/CatalogRule/Model/Indexer/RuleProductsSelectBuilder.php index 25d164aeee5c3..6989a33535ad8 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/RuleProductsSelectBuilder.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/RuleProductsSelectBuilder.php @@ -6,6 +6,10 @@ namespace Magento\CatalogRule\Model\Indexer; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\Framework\App\ObjectManager; + /** * Build select for rule relation with product. */ @@ -32,29 +36,38 @@ class RuleProductsSelectBuilder private $metadataPool; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher + * @var ActiveTableSwitcher */ private $activeTableSwitcher; + /** + * @var TableSwapper + */ + private $tableSwapper; + /** * @param \Magento\Framework\App\ResourceConnection $resource * @param \Magento\Eav\Model\Config $eavConfig * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool - * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + * @param ActiveTableSwitcher $activeTableSwitcher + * @param TableSwapper|null $tableSwapper */ public function __construct( \Magento\Framework\App\ResourceConnection $resource, \Magento\Eav\Model\Config $eavConfig, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\EntityManager\MetadataPool $metadataPool, - \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + ActiveTableSwitcher $activeTableSwitcher, + TableSwapper $tableSwapper = null ) { $this->eavConfig = $eavConfig; $this->storeManager = $storeManager; $this->metadataPool = $metadataPool; $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; + $this->tableSwapper = $tableSwapper ?? + ObjectManager::getInstance()->get(TableSwapper::class); } /** @@ -74,7 +87,7 @@ public function build( $indexTable = $this->resource->getTableName('catalogrule_product'); if ($useAdditionalTable) { $indexTable = $this->resource->getTableName( - $this->activeTableSwitcher->getAdditionalTableName('catalogrule_product') + $this->tableSwapper->getWorkingTableName('catalogrule_product') ); } diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexerTableSwapperTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexerTableSwapperTest.php new file mode 100644 index 0000000000000..654a1180d8717 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexerTableSwapperTest.php @@ -0,0 +1,168 @@ +resourceConnectionMock = $this->createMock(ResourceConnection::class); + + $this->adapterInterfaceMock = $this->getMockBuilder(AdapterInterface::class)->getMockForAbstractClass(); + /** @var \Zend_Db_Statement_Interface $statementInterfaceMock */ + $this->statementInterfaceMock = $this->getMockBuilder(\Zend_Db_Statement_Interface::class) + ->getMockForAbstractClass(); + /** @var Table $tableMock */ + $this->tableMock = $this->createMock(Table::class); + $this->resourceConnectionMock->expects($this->any()) + ->method('getConnection') + ->willReturn($this->adapterInterfaceMock); + } + + /** + * @return void + */ + public function testGetWorkingTableNameWithExistingTemporaryTable(): void + { + $model = new IndexerTableSwapper($this->resourceConnectionMock); + $originalTableName = 'catalogrule_product'; + $temporaryTableNames = ['catalogrule_product' => 'catalogrule_product__temp9604']; + $this->setObjectProperty($model, 'temporaryTables', $temporaryTableNames); + + $this->resourceConnectionMock->expects($this->once()) + ->method('getTableName') + ->with($originalTableName) + ->willReturn($originalTableName); + + $this->assertEquals( + $temporaryTableNames[$originalTableName], + $model->getWorkingTableName($originalTableName) + ); + } + + /** + * @return void + */ + public function testGetWorkingTableNameWithoutExistingTemporaryTable(): void + { + $model = new IndexerTableSwapper($this->resourceConnectionMock); + $originalTableName = 'catalogrule_product'; + $temporaryTableName = 'catalogrule_product__temp9604'; + $this->setObjectProperty($model, 'temporaryTables', []); + + $this->resourceConnectionMock->expects($this->at(0)) + ->method('getTableName') + ->with($originalTableName) + ->willReturn($originalTableName); + $this->resourceConnectionMock->expects($this->at(1)) + ->method('getTableName') + ->with($this->stringStartsWith($originalTableName . '__temp')) + ->willReturn($temporaryTableName); + + $this->assertEquals( + $temporaryTableName, + $model->getWorkingTableName($originalTableName) + ); + } + + /** + * Sets object non-public property. + * + * @param mixed $object + * @param string $propertyName + * @param mixed $value + * + * @return void + */ + private function setObjectProperty($object, string $propertyName, $value): void + { + $reflectionClass = new \ReflectionClass($object); + $reflectionProperty = $reflectionClass->getProperty($propertyName); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($object, $value); + } + + /** + * @return void + */ + public function testSwapIndexTables(): void + { + $model = $this->getMockBuilder(IndexerTableSwapper::class) + ->setMethods(['getWorkingTableName']) + ->setConstructorArgs([$this->resourceConnectionMock]) + ->getMock(); + $originalTableName = 'catalogrule_product'; + $temporaryOriginalTableName = 'catalogrule_product9604'; + $temporaryTableName = 'catalogrule_product__temp9604'; + $toRename = [ + [ + 'oldName' => $originalTableName, + 'newName' => $temporaryOriginalTableName, + ], + [ + 'oldName' => $temporaryTableName, + 'newName' => $originalTableName, + ], + ]; + + $this->resourceConnectionMock->expects($this->at(0)) + ->method('getTableName') + ->with($originalTableName) + ->willReturn($originalTableName); + $this->resourceConnectionMock->expects($this->at(1)) + ->method('getTableName') + ->with($this->stringStartsWith($originalTableName)) + ->willReturn($temporaryOriginalTableName); + $model->expects($this->once()) + ->method('getWorkingTableName') + ->with($originalTableName) + ->willReturn($temporaryTableName); + $this->adapterInterfaceMock->expects($this->once()) + ->method('renameTablesBatch') + ->with($toRename) + ->willReturn(true); + $this->adapterInterfaceMock->expects($this->once()) + ->method('dropTable') + ->with($temporaryOriginalTableName) + ->willReturn(true); + + $model->swapIndexTables([$originalTableName]); + } +} diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleGroupWebsiteTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleGroupWebsiteTest.php index d60a662193e54..f02c2c643f809 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleGroupWebsiteTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleGroupWebsiteTest.php @@ -6,6 +6,9 @@ namespace Magento\CatalogRule\Test\Unit\Model\Indexer; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface; + class ReindexRuleGroupWebsiteTest extends \PHPUnit\Framework\TestCase { /** @@ -24,10 +27,15 @@ class ReindexRuleGroupWebsiteTest extends \PHPUnit\Framework\TestCase private $resourceMock; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject */ private $activeTableSwitcherMock; + /** + * @var IndexerTableSwapperInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $tableSwapperMock; + protected function setUp() { $this->dateTimeMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\DateTime::class) @@ -37,13 +45,17 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->activeTableSwitcherMock = - $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class) + $this->getMockBuilder(ActiveTableSwitcher::class) ->disableOriginalConstructor() ->getMock(); + $this->tableSwapperMock = $this->getMockForAbstractClass( + IndexerTableSwapperInterface::class + ); $this->model = new \Magento\CatalogRule\Model\Indexer\ReindexRuleGroupWebsite( $this->dateTimeMock, $this->resourceMock, - $this->activeTableSwitcherMock + $this->activeTableSwitcherMock, + $this->tableSwapperMock ); } @@ -55,31 +67,25 @@ public function testExecute() $this->resourceMock->expects($this->at(0))->method('getConnection')->willReturn($connectionMock); $this->dateTimeMock->expects($this->once())->method('gmtTimestamp')->willReturn($timeStamp); - $this->activeTableSwitcherMock->expects($this->at(0)) - ->method('getAdditionalTableName') - ->with('catalogrule_group_website') - ->willReturn('catalogrule_group_website_replica'); - $this->activeTableSwitcherMock->expects($this->at(1)) - ->method('getAdditionalTableName') - ->with('catalogrule_product') - ->willReturn('catalogrule_product_replica'); + $this->tableSwapperMock->expects($this->any()) + ->method('getWorkingTableName') + ->willReturnMap( + [ + ['catalogrule_group_website', 'catalogrule_group_website_replica'], + ['catalogrule_product', 'catalogrule_product_replica'], + ] + ); - $this->resourceMock->expects($this->at(1)) - ->method('getTableName') - ->with('catalogrule_group_website') - ->willReturn('catalogrule_group_website'); - $this->resourceMock->expects($this->at(2)) - ->method('getTableName') - ->with('catalogrule_product') - ->willReturn('catalogrule_product'); - $this->resourceMock->expects($this->at(3)) - ->method('getTableName') - ->with('catalogrule_group_website_replica') - ->willReturn('catalogrule_group_website_replica'); - $this->resourceMock->expects($this->at(4)) + $this->resourceMock->expects($this->any()) ->method('getTableName') - ->with('catalogrule_product_replica') - ->willReturn('catalogrule_product_replica'); + ->willReturnMap( + [ + ['catalogrule_group_website', 'default', 'catalogrule_group_website'], + ['catalogrule_product', 'default', 'catalogrule_product'], + ['catalogrule_group_website_replica', 'default', 'catalogrule_group_website_replica'], + ['catalogrule_product_replica', 'default', 'catalogrule_product_replica'], + ] + ); $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) ->disableOriginalConstructor() diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php index b829468396bf0..0dbbaee8d2871 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php @@ -6,6 +6,9 @@ namespace Magento\CatalogRule\Test\Unit\Model\Indexer; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface; + class ReindexRuleProductTest extends \PHPUnit\Framework\TestCase { /** @@ -19,22 +22,30 @@ class ReindexRuleProductTest extends \PHPUnit\Framework\TestCase private $resourceMock; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject */ private $activeTableSwitcherMock; + /** + * @var IndexerTableSwapperInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $tableSwapperMock; + protected function setUp() { $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) ->disableOriginalConstructor() ->getMock(); - $this->activeTableSwitcherMock = - $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class) - ->disableOriginalConstructor() - ->getMock(); + $this->activeTableSwitcherMock = $this->getMockBuilder(ActiveTableSwitcher::class) + ->disableOriginalConstructor() + ->getMock(); + $this->tableSwapperMock = $this->getMockForAbstractClass( + IndexerTableSwapperInterface::class + ); $this->model = new \Magento\CatalogRule\Model\Indexer\ReindexRuleProduct( $this->resourceMock, - $this->activeTableSwitcherMock + $this->activeTableSwitcherMock, + $this->tableSwapperMock ); } @@ -71,8 +82,8 @@ public function testExecute() $ruleMock->expects($this->exactly(2))->method('getWebsiteIds')->willReturn(1); $ruleMock->expects($this->once())->method('getMatchingProductIds')->willReturn($productIds); - $this->activeTableSwitcherMock->expects($this->once()) - ->method('getAdditionalTableName') + $this->tableSwapperMock->expects($this->once()) + ->method('getWorkingTableName') ->with('catalogrule_product') ->willReturn('catalogrule_product_replica'); diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductPricesPersistorTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductPricesPersistorTest.php index 3efe26971627e..03163aa2d7c45 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductPricesPersistorTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductPricesPersistorTest.php @@ -6,6 +6,9 @@ namespace Magento\CatalogRule\Test\Unit\Model\Indexer; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface; + class RuleProductPricesPersistorTest extends \PHPUnit\Framework\TestCase { /** @@ -24,10 +27,15 @@ class RuleProductPricesPersistorTest extends \PHPUnit\Framework\TestCase private $resourceMock; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject */ private $activeTableSwitcherMock; + /** + * @var IndexerTableSwapperInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $tableSwapperMock; + protected function setUp() { $this->dateTimeMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime::class) @@ -36,14 +44,17 @@ protected function setUp() $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) ->disableOriginalConstructor() ->getMock(); - $this->activeTableSwitcherMock = - $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class) - ->disableOriginalConstructor() - ->getMock(); + $this->activeTableSwitcherMock = $this->getMockBuilder(ActiveTableSwitcher::class) + ->disableOriginalConstructor() + ->getMock(); + $this->tableSwapperMock = $this->getMockForAbstractClass( + IndexerTableSwapperInterface::class + ); $this->model = new \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor( $this->dateTimeMock, $this->resourceMock, - $this->activeTableSwitcherMock + $this->activeTableSwitcherMock, + $this->tableSwapperMock ); } @@ -64,8 +75,8 @@ public function testExecute() ]; $tableName = 'catalogrule_product_price_replica'; - $this->activeTableSwitcherMock->expects($this->once()) - ->method('getAdditionalTableName') + $this->tableSwapperMock->expects($this->once()) + ->method('getWorkingTableName') ->with('catalogrule_product_price') ->willReturn($tableName); @@ -120,8 +131,8 @@ public function testExecuteWithException() ]; $tableName = 'catalogrule_product_price_replica'; - $this->activeTableSwitcherMock->expects($this->once()) - ->method('getAdditionalTableName') + $this->tableSwapperMock->expects($this->once()) + ->method('getWorkingTableName') ->with('catalogrule_product_price') ->willReturn($tableName); diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductsSelectBuilderTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductsSelectBuilderTest.php index 92b4bb353f046..e43fe41dc2127 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductsSelectBuilderTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductsSelectBuilderTest.php @@ -7,6 +7,8 @@ namespace Magento\CatalogRule\Test\Unit\Model\Indexer; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Select; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; @@ -35,7 +37,7 @@ class RuleProductsSelectBuilderTest extends \PHPUnit\Framework\TestCase private $resourceMock; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject */ private $activeTableSwitcherMock; @@ -49,6 +51,11 @@ class RuleProductsSelectBuilderTest extends \PHPUnit\Framework\TestCase */ private $metadataPoolMock; + /** + * @var IndexerTableSwapperInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $tableSwapperMock; + protected function setUp() { $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) @@ -56,8 +63,7 @@ protected function setUp() $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) ->disableOriginalConstructor() ->getMock(); - $this->activeTableSwitcherMock = - $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class) + $this->activeTableSwitcherMock = $this->getMockBuilder(ActiveTableSwitcher::class) ->disableOriginalConstructor() ->getMock(); $this->eavConfigMock = $this->getMockBuilder(\Magento\Eav\Model\Config::class) @@ -66,13 +72,17 @@ protected function setUp() $this->metadataPoolMock = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) ->disableOriginalConstructor() ->getMock(); + $this->tableSwapperMock = $this->getMockForAbstractClass( + IndexerTableSwapperInterface::class + ); $this->model = new \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder( $this->resourceMock, $this->eavConfigMock, $this->storeManagerMock, $this->metadataPoolMock, - $this->activeTableSwitcherMock + $this->activeTableSwitcherMock, + $this->tableSwapperMock ); } @@ -92,8 +102,8 @@ public function testBuild() $connectionMock = $this->getMockBuilder(AdapterInterface::class)->disableOriginalConstructor()->getMock(); $this->resourceMock->expects($this->at(0))->method('getConnection')->willReturn($connectionMock); - $this->activeTableSwitcherMock->expects($this->once()) - ->method('getAdditionalTableName') + $this->tableSwapperMock->expects($this->once()) + ->method('getWorkingTableName') ->with($ruleTable) ->willReturn($rplTable); diff --git a/app/code/Magento/CatalogRule/etc/di.xml b/app/code/Magento/CatalogRule/etc/di.xml index e29f5541fd77c..40893592c3d0f 100644 --- a/app/code/Magento/CatalogRule/etc/di.xml +++ b/app/code/Magento/CatalogRule/etc/di.xml @@ -149,4 +149,5 @@ CatalogRuleCustomConditionProvider + diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php index 6bf735e2141cc..182ecf873d77a 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php @@ -12,7 +12,13 @@ use Magento\Framework\DB\Select; use Magento\Framework\Search\Request\BucketInterface; use Magento\Framework\Search\Request\Dimension; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver as TableResolver; +use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class DataProvider { /** @@ -32,20 +38,28 @@ class DataProvider */ protected $categoryFactory; + /** + * @var TableResolver + */ + private $tableResolver; + /** * DataProvider constructor. * @param ResourceConnection $resource * @param ScopeResolverInterface $scopeResolver * @param Resolver $layerResolver + * @param TableResolver|null $tableResolver */ public function __construct( ResourceConnection $resource, ScopeResolverInterface $scopeResolver, - Resolver $layerResolver + Resolver $layerResolver, + TableResolver $tableResolver = null ) { $this->resource = $resource; $this->scopeResolver = $scopeResolver; $this->layer = $layerResolver->get(); + $this->tableResolver = $tableResolver ?: ObjectManager::getInstance()->get(TableResolver::class); } /** @@ -69,9 +83,18 @@ public function aroundGetDataSet( $currentScopeId = $this->scopeResolver->getScope($dimensions['scope']->getValue())->getId(); $currentCategory = $this->layer->getCurrentCategory(); + $catalogCategoryProductDimension = new Dimension(\Magento\Store\Model\Store::ENTITY, $currentScopeId); + + $catalogCategoryProductTableName = $this->tableResolver->resolve( + AbstractAction::MAIN_INDEX_TABLE, + [ + $catalogCategoryProductDimension + ] + ); + $derivedTable = $this->resource->getConnection()->select(); $derivedTable->from( - ['main_table' => $this->resource->getTableName('catalog_category_product_index')], + ['main_table' => $catalogCategoryProductTableName], [ 'value' => 'category_id' ] diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php index dadce2ed0240c..66e0457e7fadd 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php @@ -7,6 +7,10 @@ namespace Magento\CatalogSearch\Model\Search\FilterMapper; use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver as TableResolver; +use Magento\Framework\Search\Request\Dimension; +use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; /** * Strategy which processes exclusions from general rules @@ -34,19 +38,27 @@ class ExclusionStrategy implements FilterStrategyInterface */ private $validFields = ['price', 'category_ids']; + /** + * @var TableResolver + */ + private $tableResolver; + /** * @param \Magento\Framework\App\ResourceConnection $resourceConnection * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param AliasResolver $aliasResolver + * @param TableResolver|null $tableResolver */ public function __construct( \Magento\Framework\App\ResourceConnection $resourceConnection, \Magento\Store\Model\StoreManagerInterface $storeManager, - AliasResolver $aliasResolver + AliasResolver $aliasResolver, + TableResolver $tableResolver = null ) { $this->resourceConnection = $resourceConnection; $this->storeManager = $storeManager; $this->aliasResolver = $aliasResolver; + $this->tableResolver = $tableResolver ?: ObjectManager::getInstance()->get(TableResolver::class); } /** @@ -112,7 +124,18 @@ private function applyCategoryFilter( \Magento\Framework\DB\Select $select ) { $alias = $this->aliasResolver->getAlias($filter); - $tableName = $this->resourceConnection->getTableName('catalog_category_product_index'); + + $catalogCategoryProductDimension = new Dimension( + \Magento\Store\Model\Store::ENTITY, + $this->storeManager->getStore()->getId() + ); + + $tableName = $this->tableResolver->resolve( + AbstractAction::MAIN_INDEX_TABLE, + [ + $catalogCategoryProductDimension + ] + ); $mainTableAlias = $this->extractTableAliasFromSelect($select); $select->joinInner( diff --git a/app/code/Magento/Checkout/Controller/Index/Index.php b/app/code/Magento/Checkout/Controller/Index/Index.php index 56575fb6c0607..785c1f1473be6 100644 --- a/app/code/Magento/Checkout/Controller/Index/Index.php +++ b/app/code/Magento/Checkout/Controller/Index/Index.php @@ -4,6 +4,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Checkout\Controller\Index; class Index extends \Magento\Checkout\Controller\Onepage @@ -32,11 +35,35 @@ public function execute() return $this->resultRedirectFactory->create()->setPath('checkout/cart'); } - $this->_customerSession->regenerateId(); + // generate session ID only if connection is unsecure according to issues in session_regenerate_id function. + // @see http://php.net/manual/en/function.session-regenerate-id.php + if (!$this->isSecureRequest()) { + $this->_customerSession->regenerateId(); + } $this->_objectManager->get(\Magento\Checkout\Model\Session::class)->setCartWasUpdated(false); $this->getOnepage()->initCheckout(); $resultPage = $this->resultPageFactory->create(); $resultPage->getConfig()->getTitle()->set(__('Checkout')); return $resultPage; } + + /** + * Checks if current request uses SSL and referer also is secure. + * + * @return bool + */ + private function isSecureRequest(): bool + { + $request = $this->getRequest(); + + $referrer = $request->getHeader('referer'); + $secure = false; + + if ($referrer) { + $scheme = parse_url($referrer, PHP_URL_SCHEME); + $secure = $scheme === 'https'; + } + + return $secure && $request->isSecure(); + } } diff --git a/app/code/Magento/Checkout/Test/Unit/Controller/Index/IndexTest.php b/app/code/Magento/Checkout/Test/Unit/Controller/Index/IndexTest.php index 8d105f25465e4..04723c5894f8f 100644 --- a/app/code/Magento/Checkout/Test/Unit/Controller/Index/IndexTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Controller/Index/IndexTest.php @@ -1,14 +1,23 @@ objectManager = new ObjectManager($this); - $this->objectManagerMock = $this->basicMock(\Magento\Framework\ObjectManagerInterface::class); - $this->dataMock = $this->basicMock(\Magento\Checkout\Helper\Data::class); - $this->quoteMock = $this->createPartialMock( - \Magento\Quote\Model\Quote::class, + $this->objectManagerMock = $this->basicMock(ObjectManagerInterface::class); + $this->data = $this->basicMock(Data::class); + $this->quote = $this->createPartialMock( + Quote::class, ['getHasError', 'hasItems', 'validateMinimumAmount', 'hasError'] ); $this->contextMock = $this->basicMock(\Magento\Framework\App\Action\Context::class); - $this->sessionMock = $this->basicMock(\Magento\Customer\Model\Session::class); + $this->session = $this->basicMock(Session::class); $this->onepageMock = $this->basicMock(\Magento\Checkout\Model\Type\Onepage::class); $this->layoutMock = $this->basicMock(\Magento\Framework\View\Layout::class); - $this->requestMock = $this->basicMock(\Magento\Framework\App\RequestInterface::class); + $this->request = $this->getMockBuilder(Http::class) + ->disableOriginalConstructor() + ->setMethods(['isSecure', 'getHeader']) + ->getMock(); $this->responseMock = $this->basicMock(\Magento\Framework\App\ResponseInterface::class); $this->redirectMock = $this->basicMock(\Magento\Framework\App\Response\RedirectInterface::class); - $this->resultPageMock = $this->basicMock(\Magento\Framework\View\Result\Page::class); + $this->resultPage = $this->basicMock(Page::class); $this->pageConfigMock = $this->basicMock(\Magento\Framework\View\Page\Config::class); $this->titleMock = $this->basicMock(\Magento\Framework\View\Page\Title::class); $this->url = $this->createMock(\Magento\Framework\UrlInterface::class); @@ -130,7 +142,7 @@ protected function setUp() ->getMock(); $resultPageFactoryMock->expects($this->any()) ->method('create') - ->willReturn($this->resultPageMock); + ->willReturn($this->resultPage); $resultRedirectFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\RedirectFactory::class) ->disableOriginalConstructor() @@ -141,21 +153,21 @@ protected function setUp() ->willReturn($this->resultRedirectMock); // stubs - $this->basicStub($this->onepageMock, 'getQuote')->willReturn($this->quoteMock); - $this->basicStub($this->resultPageMock, 'getLayout')->willReturn($this->layoutMock); + $this->basicStub($this->onepageMock, 'getQuote')->willReturn($this->quote); + $this->basicStub($this->resultPage, 'getLayout')->willReturn($this->layoutMock); $this->basicStub($this->layoutMock, 'getBlock') ->willReturn($this->basicMock(\Magento\Theme\Block\Html\Header::class)); - $this->basicStub($this->resultPageMock, 'getConfig')->willReturn($this->pageConfigMock); + $this->basicStub($this->resultPage, 'getConfig')->willReturn($this->pageConfigMock); $this->basicStub($this->pageConfigMock, 'getTitle')->willReturn($this->titleMock); $this->basicStub($this->titleMock, 'set')->willReturn($this->titleMock); // objectManagerMock $objectManagerReturns = [ - [\Magento\Checkout\Helper\Data::class, $this->dataMock], + [Data::class, $this->data], [\Magento\Checkout\Model\Type\Onepage::class, $this->onepageMock], [\Magento\Checkout\Model\Session::class, $this->basicMock(\Magento\Checkout\Model\Session::class)], - [\Magento\Customer\Model\Session::class, $this->basicMock(\Magento\Customer\Model\Session::class)], + [Session::class, $this->basicMock(Session::class)], ]; $this->objectManagerMock->expects($this->any()) @@ -165,7 +177,7 @@ protected function setUp() ->willReturn($this->basicMock(\Magento\Framework\UrlInterface::class)); // context stubs $this->basicStub($this->contextMock, 'getObjectManager')->willReturn($this->objectManagerMock); - $this->basicStub($this->contextMock, 'getRequest')->willReturn($this->requestMock); + $this->basicStub($this->contextMock, 'getRequest')->willReturn($this->request); $this->basicStub($this->contextMock, 'getResponse')->willReturn($this->responseMock); $this->basicStub($this->contextMock, 'getMessageManager') ->willReturn($this->basicMock(\Magento\Framework\Message\ManagerInterface::class)); @@ -175,33 +187,82 @@ protected function setUp() // SUT $this->model = $this->objectManager->getObject( - \Magento\Checkout\Controller\Index\Index::class, + Index::class, [ 'context' => $this->contextMock, - 'customerSession' => $this->sessionMock, + 'customerSession' => $this->session, 'resultPageFactory' => $resultPageFactoryMock, 'resultRedirectFactory' => $resultRedirectFactoryMock ] ); } - public function testRegenerateSessionIdOnExecute() + /** + * Checks a case when session should be or not regenerated during the request. + * + * @param bool $secure + * @param string $referer + * @param InvokedCount $expectedCall + * @dataProvider sessionRegenerationDataProvider + */ + public function testRegenerateSessionIdOnExecute(bool $secure, string $referer, InvokedCount $expectedCall) + { + $this->data->method('canOnepageCheckout') + ->willReturn(true); + $this->quote->method('hasItems') + ->willReturn(true); + $this->quote->method('getHasError') + ->willReturn(false); + $this->quote->method('validateMinimumAmount') + ->willReturn(true); + $this->session->method('isLoggedIn') + ->willReturn(true); + $this->request->method('isSecure') + ->willReturn($secure); + $this->request->method('getHeader') + ->with('referer') + ->willReturn($referer); + + $this->session->expects($expectedCall) + ->method('regenerateId'); + $this->assertSame($this->resultPage, $this->model->execute()); + } + + /** + * Gets list of variations for generating new session. + * + * @return array + */ + public function sessionRegenerationDataProvider(): array { - //Stubs to control execution flow - $this->basicStub($this->dataMock, 'canOnepageCheckout')->willReturn(true); - $this->basicStub($this->quoteMock, 'hasItems')->willReturn(true); - $this->basicStub($this->quoteMock, 'getHasError')->willReturn(false); - $this->basicStub($this->quoteMock, 'validateMinimumAmount')->willReturn(true); - $this->basicStub($this->sessionMock, 'isLoggedIn')->willReturn(true); - - //Expected outcomes - $this->sessionMock->expects($this->once())->method('regenerateId'); - $this->assertSame($this->resultPageMock, $this->model->execute()); + return [ + [ + 'secure' => false, + 'referer' => 'https://test.domain.com/', + 'expectedCall' => self::once() + ], + [ + 'secure' => true, + 'referer' => false, + 'expectedCall' => self::once() + ], + [ + 'secure' => true, + 'referer' => 'http://test.domain.com/', + 'expectedCall' => self::once() + ], + // This is the only case in which session regeneration can be skipped + [ + 'secure' => true, + 'referer' => 'https://test.domain.com/', + 'expectedCall' => self::never() + ], + ]; } public function testOnepageCheckoutNotAvailable() { - $this->basicStub($this->dataMock, 'canOnepageCheckout')->willReturn(false); + $this->basicStub($this->data, 'canOnepageCheckout')->willReturn(false); $expectedPath = 'checkout/cart'; $this->resultRedirectMock->expects($this->once()) @@ -214,7 +275,7 @@ public function testOnepageCheckoutNotAvailable() public function testInvalidQuote() { - $this->basicStub($this->quoteMock, 'hasError')->willReturn(true); + $this->basicStub($this->quote, 'hasError')->willReturn(true); $expectedPath = 'checkout/cart'; $this->resultRedirectMock->expects($this->once()) @@ -226,23 +287,22 @@ public function testInvalidQuote() } /** - * @param \PHPUnit_Framework_MockObject_MockObject $mock + * @param MockObject $mock * @param string $method * - * @return \PHPUnit\Framework\MockObject_Builder_InvocationMocker + * @return InvocationMocker */ - private function basicStub($mock, $method) + private function basicStub($mock, $method): InvocationMocker { - return $mock->expects($this->any()) - ->method($method) - ->withAnyParameters(); + return $mock->method($method) + ->withAnyParameters(); } /** * @param string $className - * @return \PHPUnit_Framework_MockObject_MockObject + * @return MockObject */ - private function basicMock($className) + private function basicMock(string $className): MockObject { return $this->getMockBuilder($className) ->disableOriginalConstructor() diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js b/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js index 3827a174b3396..c707792111c82 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js @@ -66,7 +66,7 @@ define([ * @param {*} sortOrder */ registerStep: function (code, alias, title, isVisible, navigate, sortOrder) { - var hash; + var hash, active; if ($.inArray(code, this.validCodes) !== -1) { throw new DOMException('Step code [' + code + '] already registered in step navigator'); @@ -87,6 +87,12 @@ define([ navigate: navigate, sortOrder: sortOrder }); + active = this.getActiveItemIndex(); + steps.each(function (elem, index) { + if (active !== index) { + elem.isVisible(false); + } + }); this.stepCodes.push(code); hash = window.location.hash.replace('#', ''); @@ -111,10 +117,14 @@ define([ getActiveItemIndex: function () { var activeIndex = 0; - steps().sort(this.sortItems).forEach(function (element, index) { + steps().sort(this.sortItems).some(function (element, index) { if (element.isVisible()) { activeIndex = index; + + return true; } + + return false; }); return activeIndex; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index 13a2b524e5186..dab40f026645d 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -105,6 +105,13 @@ define([ self._showItemButton($(event.target)); }; + /** + * @param {jQuery.Event} event + */ + events['change ' + this.options.item.qty] = function (event) { + self._showItemButton($(event.target)); + }; + /** * @param {jQuery.Event} event */ diff --git a/app/code/Magento/Customer/Model/EmailNotification.php b/app/code/Magento/Customer/Model/EmailNotification.php index 3e326e087dd5f..4b65dcca0973f 100644 --- a/app/code/Magento/Customer/Model/EmailNotification.php +++ b/app/code/Magento/Customer/Model/EmailNotification.php @@ -314,9 +314,9 @@ private function getWebsiteStoreId($customer, $defaultStoreId = null) */ public function passwordReminder(CustomerInterface $customer) { - $storeId = $this->getWebsiteStoreId($customer); + $storeId = $customer->getStoreId(); if (!$storeId) { - $storeId = $this->storeManager->getStore()->getId(); + $storeId = $this->getWebsiteStoreId($customer); } $customerEmailData = $this->getFullCustomerObject($customer); diff --git a/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php b/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php index 61e58af78fcdf..318023d8068c5 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php @@ -312,6 +312,7 @@ public function sendNotificationEmailsDataProvider() public function testPasswordReminder() { $customerId = 1; + $customerWebsiteId = 1; $customerStoreId = 2; $customerEmail = 'email@email.com'; $customerData = ['key' => 'value']; @@ -319,6 +320,7 @@ public function testPasswordReminder() $templateIdentifier = 'Template Identifier'; $sender = 'Sender'; $senderValues = ['name' => $sender, 'email' => $sender]; + $storeIds = [1, 2]; $this->senderResolverMock ->expects($this->once()) @@ -328,6 +330,9 @@ public function testPasswordReminder() /** @var CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customer */ $customer = $this->createMock(CustomerInterface::class); + $customer->expects($this->any()) + ->method('getWebsiteId') + ->willReturn($customerWebsiteId); $customer->expects($this->any()) ->method('getStoreId') ->willReturn($customerStoreId); @@ -346,10 +351,15 @@ public function testPasswordReminder() ->method('getStore') ->willReturn($this->storeMock); - $this->storeManagerMock->expects($this->at(1)) - ->method('getStore') - ->with($customerStoreId) - ->willReturn($this->storeMock); + $websiteMock = $this->createPartialMock(\Magento\Store\Model\Website::class, ['getStoreIds']); + $websiteMock->expects($this->any()) + ->method('getStoreIds') + ->willReturn($storeIds); + + $this->storeManagerMock->expects($this->any()) + ->method('getWebsite') + ->with($customerWebsiteId) + ->willReturn($websiteMock); $this->customerRegistryMock->expects($this->once()) ->method('retrieveSecureData') @@ -396,6 +406,99 @@ public function testPasswordReminder() $this->model->passwordReminder($customer); } + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testPasswordReminderCustomerWithoutStoreId() + { + $customerId = 1; + $customerWebsiteId = 1; + $customerStoreId = null; + $customerEmail = 'email@email.com'; + $customerData = ['key' => 'value']; + $customerName = 'Customer Name'; + $templateIdentifier = 'Template Identifier'; + $sender = 'Sender'; + $senderValues = ['name' => $sender, 'email' => $sender]; + $storeIds = [1, 2]; + $defaultStoreId = reset($storeIds); + $this->senderResolverMock + ->expects($this->once()) + ->method('resolve') + ->with($sender, $defaultStoreId) + ->willReturn($senderValues); + /** @var CustomerInterface | \PHPUnit_Framework_MockObject_MockObject $customer */ + $customer = $this->createMock(CustomerInterface::class); + $customer->expects($this->any()) + ->method('getWebsiteId') + ->willReturn($customerWebsiteId); + $customer->expects($this->any()) + ->method('getStoreId') + ->willReturn($customerStoreId); + $customer->expects($this->any()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->any()) + ->method('getEmail') + ->willReturn($customerEmail); + $this->storeMock->expects($this->any()) + ->method('getId') + ->willReturn($defaultStoreId); + $this->storeManagerMock->expects($this->at(0)) + ->method('getStore') + ->willReturn($this->storeMock); + $this->storeManagerMock->expects($this->at(1)) + ->method('getStore') + ->with($defaultStoreId) + ->willReturn($this->storeMock); + $websiteMock = $this->createPartialMock(\Magento\Store\Model\Website::class, ['getStoreIds']); + $websiteMock->expects($this->any()) + ->method('getStoreIds') + ->willReturn($storeIds); + $this->storeManagerMock->expects($this->any()) + ->method('getWebsite') + ->with($customerWebsiteId) + ->willReturn($websiteMock); + + $this->customerRegistryMock->expects($this->once()) + ->method('retrieveSecureData') + ->with($customerId) + ->willReturn($this->customerSecureMock); + $this->dataProcessorMock->expects($this->once()) + ->method('buildOutputDataArray') + ->with($customer, CustomerInterface::class) + ->willReturn($customerData); + $this->customerViewHelperMock->expects($this->any()) + ->method('getCustomerName') + ->with($customer) + ->willReturn($customerName); + $this->customerSecureMock->expects($this->once()) + ->method('addData') + ->with($customerData) + ->willReturnSelf(); + $this->customerSecureMock->expects($this->once()) + ->method('setData') + ->with('name', $customerName) + ->willReturnSelf(); + $this->scopeConfigMock->expects($this->at(0)) + ->method('getValue') + ->with(EmailNotification::XML_PATH_REMIND_EMAIL_TEMPLATE, ScopeInterface::SCOPE_STORE, $defaultStoreId) + ->willReturn($templateIdentifier); + $this->scopeConfigMock->expects($this->at(1)) + ->method('getValue') + ->with(EmailNotification::XML_PATH_FORGOT_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, $defaultStoreId) + ->willReturn($sender); + $this->mockDefaultTransportBuilder( + $templateIdentifier, + $defaultStoreId, + $senderValues, + $customerEmail, + $customerName, + ['customer' => $this->customerSecureMock, 'store' => $this->storeMock] + ); + $this->model->passwordReminder($customer); + } + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index 016dca2fa526c..ad405190262e4 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -378,19 +378,11 @@ protected function _prepareDataForUpdate(array $rowData) $this->_newCustomers[$emailInLowercase][$rowData[self::COLUMN_WEBSITE]] = $entityId; } - $entityRow = [ - 'group_id' => empty($rowData['group_id']) ? self::DEFAULT_GROUP_ID : $rowData['group_id'], - 'store_id' => empty($rowData[self::COLUMN_STORE]) ? 0 : $this->_storeCodeToId[$rowData[self::COLUMN_STORE]], - 'created_at' => $createdAt->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT), - 'updated_at' => $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT), - 'entity_id' => $entityId, - ]; - // password change/set if (isset($rowData['password']) && strlen($rowData['password'])) { $rowData['password_hash'] = $this->_customerModel->hashPassword($rowData['password']); } - + $entityRow = ['entity_id' => $entityId]; // attribute values foreach (array_intersect_key($rowData, $this->_attributes) as $attributeCode => $value) { $attributeParameters = $this->_attributes[$attributeCode]; @@ -429,12 +421,21 @@ protected function _prepareDataForUpdate(array $rowData) if ($newCustomer) { // create + $entityRow['group_id'] = empty($rowData['group_id']) ? self::DEFAULT_GROUP_ID : $rowData['group_id']; + $entityRow['store_id'] = empty($rowData[self::COLUMN_STORE]) + ? \Magento\Store\Model\Store::DEFAULT_STORE_ID : $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; + $entityRow['created_at'] = $createdAt->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); + $entityRow['updated_at'] = $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); $entityRow['website_id'] = $this->_websiteCodeToId[$rowData[self::COLUMN_WEBSITE]]; $entityRow['email'] = $emailInLowercase; $entityRow['is_active'] = 1; $entitiesToCreate[] = $entityRow; } else { // edit + $entityRow['updated_at'] = $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); + if (!empty($rowData[self::COLUMN_STORE])) { + $entityRow['store_id'] = $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; + } $entitiesToUpdate[] = $entityRow; } diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php index 67a0bbefe7510..3d4c9e89a035f 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php @@ -165,7 +165,7 @@ public function getValue(\Magento\Framework\DataObject $object) $options = $opt->getAllOptions(); if ($options) { foreach ($options as $option) { - if ($option['value'] == $value) { + if ($option['value'] === $value) { $valueOption = $option['label']; } } diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php index 7e992273a68dc..c11fb64cde7e6 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php @@ -93,6 +93,11 @@ class IndexTest extends \PHPUnit\Framework\TestCase */ protected $storeInterface; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $tableResolver; + /** * Setup * @@ -210,6 +215,30 @@ protected function setUp() ->willReturn('entity_id'); $objectManager = new ObjectManagerHelper($this); + + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $resource = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->setMethods([ + 'getConnection', + 'getTableName' + ]) + ->disableOriginalConstructor() + ->getMock(); + $resource->expects($this->any()) + ->method('getConnection') + ->willReturn($connection); + $resource->expects($this->any())->method('getTableName')->willReturnArgument(0); + + $this->tableResolver = $objectManager->getObject( + \Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver::class, + [ + 'resource' => $resource + ] + ); + $this->model = $objectManager->getObject( \Magento\Elasticsearch\Model\ResourceModel\Index::class, [ @@ -220,6 +249,7 @@ protected function setUp() 'categoryRepository' => $this->categoryRepository, 'eavConfig' => $this->eavConfig, 'connectionName' => 'default', + 'tableResolver' => $this->tableResolver ] ); } @@ -318,7 +348,7 @@ public function testGetCategoryProductIndexData() $select->expects($this->any()) ->method('from') ->with( - [null], + ['catalog_category_product_index_store1'], ['category_id', 'product_id', 'position', 'store_id'] )->willReturnSelf(); @@ -498,7 +528,7 @@ public function testGetFullCategoryProductIndexData() $this->assertInternalType( 'array', - $this->model->getFullCategoryProductIndexData([1, [1, ]]) + $this->model->getFullCategoryProductIndexData(1, [1, ]) ); } diff --git a/app/code/Magento/Indexer/Model/Indexer.php b/app/code/Magento/Indexer/Model/Indexer.php index 58b2e9ed76a78..87a7cce58e1a5 100644 --- a/app/code/Magento/Indexer/Model/Indexer.php +++ b/app/code/Magento/Indexer/Model/Indexer.php @@ -398,7 +398,7 @@ protected function getStructureInstance() * Regenerate full index * * @return void - * @throws \Exception + * @throws \Throwable */ public function reindexAll() { @@ -414,16 +414,11 @@ public function reindexAll() $state->setStatus(StateInterface::STATUS_VALID); $state->save(); $this->getView()->resume(); - } catch (\Exception $exception) { + } catch (\Throwable $exception) { $state->setStatus(StateInterface::STATUS_INVALID); $state->save(); $this->getView()->resume(); throw $exception; - } catch (\Error $error) { - $state->setStatus(StateInterface::STATUS_INVALID); - $state->save(); - $this->getView()->resume(); - throw $error; } } } diff --git a/app/code/Magento/Payment/Helper/Data.php b/app/code/Magento/Payment/Helper/Data.php index f3565ea324290..5fd23c195f0c4 100644 --- a/app/code/Magento/Payment/Helper/Data.php +++ b/app/code/Magento/Payment/Helper/Data.php @@ -293,7 +293,9 @@ public function getPaymentMethodList($sorted = true, $asLabelValue = false, $wit foreach ($methods as $code => $title) { if (isset($groups[$code])) { $labelValues[$code]['label'] = $title; - $labelValues[$code]['value'] = null; + if (!isset($labelValues[$code]['value'])) { + $labelValues[$code]['value'] = null; + } } elseif (isset($groupRelations[$code])) { unset($labelValues[$code]); $labelValues[$groupRelations[$code]]['value'][$code] = ['value' => $code, 'label' => $title]; diff --git a/app/code/Magento/Payment/Model/Method/AbstractMethod.php b/app/code/Magento/Payment/Model/Method/AbstractMethod.php index 6e6a6773bd5f2..33200014c7ec1 100644 --- a/app/code/Magento/Payment/Model/Method/AbstractMethod.php +++ b/app/code/Magento/Payment/Model/Method/AbstractMethod.php @@ -6,12 +6,14 @@ namespace Magento\Payment\Model\Method; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject; use Magento\Payment\Model\InfoInterface; use Magento\Payment\Model\MethodInterface; use Magento\Payment\Observer\AbstractDataAssignObserver; use Magento\Quote\Api\Data\PaymentMethodInterface; use Magento\Sales\Model\Order\Payment; +use Magento\Directory\Helper\Data as DirectoryHelper; /** * Payment method abstract model @@ -194,6 +196,11 @@ abstract class AbstractMethod extends \Magento\Framework\Model\AbstractExtensibl */ protected $logger; + /** + * @var DirectoryHelper + */ + private $directory; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -205,6 +212,7 @@ abstract class AbstractMethod extends \Magento\Framework\Model\AbstractExtensibl * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data + * @param DirectoryHelper $directory * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -217,7 +225,8 @@ public function __construct( \Magento\Payment\Model\Method\Logger $logger, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + DirectoryHelper $directory = null ) { parent::__construct( $context, @@ -231,6 +240,7 @@ public function __construct( $this->_paymentData = $paymentData; $this->_scopeConfig = $scopeConfig; $this->logger = $logger; + $this->directory = $directory ?: ObjectManager::getInstance()->get(DirectoryHelper::class); $this->initializeData($data); } @@ -584,11 +594,14 @@ public function validate() } else { $billingCountry = $paymentInfo->getQuote()->getBillingAddress()->getCountryId(); } + $billingCountry = $billingCountry ?: $this->directory->getDefaultCountry(); + if (!$this->canUseForCountry($billingCountry)) { throw new \Magento\Framework\Exception\LocalizedException( __('You can\'t use the payment type you selected to make payments to the billing country.') ); } + return $this; } diff --git a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php index eb4c35b02696c..f0fce97da512a 100644 --- a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php +++ b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php @@ -7,6 +7,7 @@ namespace Magento\Paypal\Controller\Express\AbstractExpress; +use Magento\Framework\Exception\LocalizedException; use Magento\Paypal\Model\Api\ProcessableException as ApiProcessableException; /** @@ -118,15 +119,27 @@ public function execute() return; } catch (ApiProcessableException $e) { $this->_processPaypalApiError($e); + } catch (LocalizedException $e) { + $this->processException($e, $e->getRawMessage()); } catch (\Exception $e) { - $this->messageManager->addExceptionMessage( - $e, - __('We can\'t place the order.') - ); - $this->_redirect('*/*/review'); + $this->processException($e, 'We can\'t place the order.'); } } + /** + * Process exception. + * + * @param \Exception $exception + * @param string $message + * + * @return void + */ + private function processException(\Exception $exception, string $message): void + { + $this->messageManager->addExceptionMessage($exception, __($message)); + $this->_redirect('*/*/review'); + } + /** * Process PayPal API's processable errors * diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php index e9f2c1b8415a8..9c9b4dc3e87a7 100644 --- a/app/code/Magento/Paypal/Model/Express/Checkout.php +++ b/app/code/Magento/Paypal/Model/Express/Checkout.php @@ -809,7 +809,9 @@ public function place($token, $shippingMethodCode = null) case \Magento\Sales\Model\Order::STATE_PROCESSING: case \Magento\Sales\Model\Order::STATE_COMPLETE: case \Magento\Sales\Model\Order::STATE_PAYMENT_REVIEW: - $this->orderSender->send($order); + if (!$order->getEmailSent()) { + $this->orderSender->send($order); + } $this->_checkoutSession->start(); break; default: @@ -897,7 +899,12 @@ protected function _setExportedAddressData($address, $exportedAddress) { // Exported data is more priority if we came from Express Checkout button $isButton = (bool)$this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON); - if (!$isButton) { + + // Since country is required field for billing and shipping address, + // we consider the address information to be empty if country is empty. + $isEmptyAddress = ($address->getCountryId() === null); + + if (!$isButton && !$isEmptyAddress) { return; } diff --git a/app/code/Magento/Quote/Model/Quote.php b/app/code/Magento/Quote/Model/Quote.php index 84034adfe9f22..ad90b75cbb0d5 100644 --- a/app/code/Magento/Quote/Model/Quote.php +++ b/app/code/Magento/Quote/Model/Quote.php @@ -14,6 +14,7 @@ use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\Quote\Address\Total as AddressTotal; use Magento\Sales\Model\Status; +use Magento\Framework\App\ObjectManager; /** * Quote model @@ -353,6 +354,11 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C */ protected $shippingAddressesItems; + /** + * @var \Magento\Sales\Model\OrderIncrementIdChecker + */ + private $orderIncrementIdChecker; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -394,6 +400,7 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection * @param array $data + * @param \Magento\Sales\Model\OrderIncrementIdChecker|null $orderIncrementIdChecker * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -436,7 +443,8 @@ public function __construct( \Magento\Quote\Model\ShippingAssignmentFactory $shippingAssignmentFactory, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + \Magento\Sales\Model\OrderIncrementIdChecker $orderIncrementIdChecker = null ) { $this->quoteValidator = $quoteValidator; $this->_catalogProduct = $catalogProduct; @@ -471,6 +479,8 @@ public function __construct( $this->totalsReader = $totalsReader; $this->shippingFactory = $shippingFactory; $this->shippingAssignmentFactory = $shippingAssignmentFactory; + $this->orderIncrementIdChecker = $orderIncrementIdChecker ?: ObjectManager::getInstance() + ->get(\Magento\Sales\Model\OrderIncrementIdChecker::class); parent::__construct( $context, $registry, @@ -2184,7 +2194,7 @@ public function reserveOrderId() } else { //checking if reserved order id was already used for some order //if yes reserving new one if not using old one - if ($this->_getResource()->isOrderIncrementIdUsed($this->getReservedOrderId())) { + if ($this->orderIncrementIdChecker->isIncrementIdUsed($this->getReservedOrderId())) { $this->setReservedOrderId($this->_getResource()->getReservedOrderId($this)); } } diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote.php b/app/code/Magento/Quote/Model/ResourceModel/Quote.php index 571f1b4686bc1..946c0e0c5f3b8 100644 --- a/app/code/Magento/Quote/Model/ResourceModel/Quote.php +++ b/app/code/Magento/Quote/Model/ResourceModel/Quote.php @@ -170,29 +170,6 @@ public function getReservedOrderId($quote) ->getNextValue(); } - /** - * Check if order increment ID is already used. - * Method can be used to avoid collisions of order IDs. - * - * @param int $orderIncrementId - * @return bool - */ - public function isOrderIncrementIdUsed($orderIncrementId) - { - /** @var \Magento\Framework\DB\Adapter\AdapterInterface $adapter */ - $adapter = $this->_resources->getConnection('sales'); - $bind = [':increment_id' => $orderIncrementId]; - /** @var \Magento\Framework\DB\Select $select */ - $select = $adapter->select(); - $select->from($this->getTable('sales_order'), 'entity_id')->where('increment_id = :increment_id'); - $entity_id = $adapter->fetchOne($select, $bind); - if ($entity_id > 0) { - return true; - } - - return false; - } - /** * Mark quotes - that depend on catalog price rules - to be recollected on demand * diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php index 451382abb4684..a92d360bad35b 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php @@ -145,6 +145,11 @@ class QuoteTest extends \PHPUnit\Framework\TestCase */ private $itemProcessor; + /** + * @var \Magento\Sales\Model\OrderIncrementIdChecker|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderIncrementIdChecker; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -256,6 +261,7 @@ protected function setUp() \Magento\Customer\Api\Data\CustomerInterfaceFactory::class, ['create'] ); + $this->orderIncrementIdChecker = $this->createMock(\Magento\Sales\Model\OrderIncrementIdChecker::class); $this->quote = (new ObjectManager($this)) ->getObject( \Magento\Quote\Model\Quote::class, @@ -280,9 +286,10 @@ protected function setUp() 'extensionAttributesJoinProcessor' => $this->extensionAttributesJoinProcessorMock, 'customerDataFactory' => $this->customerDataFactoryMock, 'itemProcessor' => $this->itemProcessor, + 'orderIncrementIdChecker' => $this->orderIncrementIdChecker, 'data' => [ - 'reserved_order_id' => 1000001 - ] + 'reserved_order_id' => 1000001, + ], ] ); } @@ -1222,28 +1229,32 @@ public function testGetAllItems() } /** - * Test to verify if existing reserved_order_id in use + * Test to verify if existing reserved_order_id in use. * * @param bool $isReservedOrderIdExist * @param int $reservedOrderId + * @return void * @dataProvider reservedOrderIdDataProvider */ - public function testReserveOrderId($isReservedOrderIdExist, $reservedOrderId) + public function testReserveOrderId(bool $isReservedOrderIdExist, int $reservedOrderId): void { - $this->resourceMock + $this->orderIncrementIdChecker ->expects($this->once()) - ->method('isOrderIncrementIdUsed') + ->method('isIncrementIdUsed') ->with(1000001)->willReturn($isReservedOrderIdExist); $this->resourceMock->expects($this->any())->method('getReservedOrderId')->willReturn($reservedOrderId); $this->quote->reserveOrderId(); $this->assertEquals($reservedOrderId, $this->quote->getReservedOrderId()); } - public function reservedOrderIdDataProvider() + /** + * @return array + */ + public function reservedOrderIdDataProvider(): array { return [ 'id_already_in_use' => [true, 100002], - 'id_not_in_use' => [false, 1000001] + 'id_not_in_use' => [false, 1000001], ]; } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/ResourceModel/QuoteTest.php b/app/code/Magento/Quote/Test/Unit/Model/ResourceModel/QuoteTest.php index a121cd55a98b4..7136e8260a880 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/ResourceModel/QuoteTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/ResourceModel/QuoteTest.php @@ -6,35 +6,17 @@ namespace Magento\Quote\Test\Unit\Model\ResourceModel; -use Magento\Framework\App\ResourceConnection; -use Magento\Framework\DB\Adapter\Pdo\Mysql; -use Magento\Framework\DB\Select; use Magento\Framework\DB\Sequence\SequenceInterface; -use Magento\Framework\Model\ResourceModel\Db\Context; -use Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationComposite; -use Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Quote\Model\Quote; use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\SalesSequence\Model\Manager; +/** + * Unit test for \Magento\Quote\Model\ResourceModel\Quote. + */ class QuoteTest extends \PHPUnit\Framework\TestCase { - /** - * @var ResourceConnection - */ - private $resourceMock; - - /** - * @var Mysql - */ - private $adapterMock; - - /** - * @var Select - */ - private $selectMock; - /** * @var Quote|\PHPUnit_Framework_MockObject_MockObject */ @@ -55,98 +37,31 @@ class QuoteTest extends \PHPUnit\Framework\TestCase */ private $model; + /** + * @inheritdoc + */ protected function setUp() { $objectManagerHelper = new ObjectManager($this); - $this->selectMock = $this->getMockBuilder(Select::class) - ->disableOriginalConstructor() - ->getMock(); - $this->selectMock->expects($this->any())->method('from')->will($this->returnSelf()); - $this->selectMock->expects($this->any())->method('where'); - - $this->adapterMock = $this->getMockBuilder(Mysql::class) - ->disableOriginalConstructor() - ->getMock(); - $this->adapterMock->expects($this->any())->method('select')->will($this->returnValue($this->selectMock)); - - $this->resourceMock = $this->getMockBuilder(ResourceConnection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resourceMock->expects( - $this->any() - )->method( - 'getConnection' - )->will( - $this->returnValue($this->adapterMock) - ); - - $context = $this->getMockBuilder(Context::class) - ->disableOriginalConstructor() - ->getMock(); - $context->expects( - $this->once() - )->method( - 'getResources' - )->will( - $this->returnValue($this->resourceMock) - ); - - $snapshot = $this->getMockBuilder(Snapshot::class) - ->disableOriginalConstructor() - ->getMock(); - $relationComposite = $this->getMockBuilder(RelationComposite::class) - ->disableOriginalConstructor() - ->getMock(); - $this->quoteMock = $this->getMockBuilder(Quote::class) - ->disableOriginalConstructor() - ->getMock(); - $this->sequenceManagerMock = $this->getMockBuilder(Manager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->sequenceMock = $this->getMockBuilder(SequenceInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $this->quoteMock = $this->createMock(Quote::class); + $this->sequenceManagerMock = $this->createMock(Manager::class); + $this->sequenceMock = $this->createMock(SequenceInterface::class); $this->model = $objectManagerHelper->getObject( QuoteResource::class, [ - 'context' => $context, - 'entitySnapshot' => $snapshot, - 'entityRelationComposite' => $relationComposite, 'sequenceManager' => $this->sequenceManagerMock, - 'connectionName' => null, ] ); } /** - * Unit test to verify if isOrderIncrementIdUsed method works with different types increment ids - * - * @param array $value - * @dataProvider isOrderIncrementIdUsedDataProvider - */ - public function testIsOrderIncrementIdUsed($value) - { - $expectedBind = [':increment_id' => $value]; - $this->adapterMock->expects($this->once())->method('fetchOne')->with($this->selectMock, $expectedBind); - $this->model->isOrderIncrementIdUsed($value); - } - - /** - * @return array - */ - public function isOrderIncrementIdUsedDataProvider() - { - return [[100000001], ['10000000001'], ['M10000000001']]; - } - - /** - * /** - * @param $entityType - * @param $storeId - * @param $reservedOrderId + * @param string $entityType + * @param int $storeId + * @param string $reservedOrderId + * @return void * @dataProvider getReservedOrderIdDataProvider */ - public function testGetReservedOrderId($entityType, $storeId, $reservedOrderId) + public function testGetReservedOrderId(string $entityType, int $storeId, string $reservedOrderId): void { $this->sequenceManagerMock->expects($this->once()) ->method('getSequence') @@ -170,7 +85,7 @@ public function getReservedOrderIdDataProvider(): array return [ [\Magento\Sales\Model\Order::ENTITY, 1, '1000000001'], [\Magento\Sales\Model\Order::ENTITY, 2, '2000000001'], - [\Magento\Sales\Model\Order::ENTITY, 3, '3000000001'] + [\Magento\Sales\Model\Order::ENTITY, 3, '3000000001'], ]; } } diff --git a/app/code/Magento/Quote/etc/db_schema.xml b/app/code/Magento/Quote/etc/db_schema.xml index 999d23328e0ec..3bd7122e65d7f 100644 --- a/app/code/Magento/Quote/etc/db_schema.xml +++ b/app/code/Magento/Quote/etc/db_schema.xml @@ -61,7 +61,7 @@ identity="false" default="1" comment="Customer Note Notify"/> - + diff --git a/app/code/Magento/Review/Block/Product/ReviewRenderer.php b/app/code/Magento/Review/Block/Product/ReviewRenderer.php index 8aa10d2437cbb..3cd15aba30420 100644 --- a/app/code/Magento/Review/Block/Product/ReviewRenderer.php +++ b/app/code/Magento/Review/Block/Product/ReviewRenderer.php @@ -18,8 +18,8 @@ class ReviewRenderer extends \Magento\Framework\View\Element\Template implements * @var array */ protected $_availableTemplates = [ - self::FULL_VIEW => 'helper/summary.phtml', - self::SHORT_VIEW => 'helper/summary_short.phtml', + self::FULL_VIEW => 'Magento_Review::helper/summary.phtml', + self::SHORT_VIEW => 'Magento_Review::helper/summary_short.phtml', ]; /** diff --git a/app/code/Magento/Review/view/frontend/templates/redirect.phtml b/app/code/Magento/Review/view/frontend/templates/redirect.phtml index fc74cadacb319..2fdb5e90a9c18 100644 --- a/app/code/Magento/Review/view/frontend/templates/redirect.phtml +++ b/app/code/Magento/Review/view/frontend/templates/redirect.phtml @@ -8,9 +8,6 @@ ?> getProduct()->getProductUrl()}#info-product_reviews"); exit; ?> diff --git a/app/code/Magento/Review/view/frontend/templates/view.phtml b/app/code/Magento/Review/view/frontend/templates/view.phtml index 564a6e1a7c537..205ce5c20572a 100644 --- a/app/code/Magento/Review/view/frontend/templates/view.phtml +++ b/app/code/Magento/Review/view/frontend/templates/view.phtml @@ -49,7 +49,7 @@ - = $block->escapeHtml(__('Back to Product Reviews')) ?> + = $block->escapeHtml(__('Back to Product Reviews')) ?> diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index 96f71133cb832..e37ee263b8301 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -3,12 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Rss\Model; use Magento\Framework\App\ObjectManager; use Magento\Framework\App\Rss\DataProviderInterface; use Magento\Framework\Serialize\SerializerInterface; -use Zend\Feed\Writer\FeedFactory; +use Magento\Framework\App\FeedFactoryInterface; /** * Provides functionality to work with RSS feeds @@ -28,6 +31,11 @@ class Rss */ protected $cache; + /** + * @var \Magento\Framework\App\FeedFactoryInterface + */ + private $feedFactory; + /** * @var SerializerInterface */ @@ -38,13 +46,16 @@ class Rss * * @param \Magento\Framework\App\CacheInterface $cache * @param SerializerInterface|null $serializer + * @param FeedFactoryInterface|null $feedFactory */ public function __construct( \Magento\Framework\App\CacheInterface $cache, - SerializerInterface $serializer = null + SerializerInterface $serializer = null, + FeedFactoryInterface $feedFactory = null ) { $this->cache = $cache; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); + $this->feedFactory = $feedFactory ?: ObjectManager::getInstance()->get(FeedFactoryInterface::class); } /** @@ -90,10 +101,12 @@ public function setDataProvider(DataProviderInterface $dataProvider) /** * @return string + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\RuntimeException */ public function createRssXml() { - $feed = FeedFactory::factory($this->getFeeds()); - return $feed->export('rss'); + $feed = $this->feedFactory->create($this->getFeeds(), FeedFactoryInterface::FORMAT_RSS); + return $feed->getFormattedContent(); } } diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php index 58fd541bab8cb..a601f8fb2d1d7 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php @@ -104,14 +104,22 @@ public function testExecuteWithException() $dataProvider = $this->createMock(\Magento\Framework\App\Rss\DataProviderInterface::class); $dataProvider->expects($this->once())->method('isAllowed')->will($this->returnValue(true)); - $rssModel = $this->createPartialMock(\Magento\Rss\Model\Rss::class, ['setDataProvider']); + $rssModel = $this->createPartialMock(\Magento\Rss\Model\Rss::class, ['setDataProvider', 'createRssXml']); $rssModel->expects($this->once())->method('setDataProvider')->will($this->returnSelf()); + $exceptionMock = new \Magento\Framework\Exception\RuntimeException( + new \Magento\Framework\Phrase('Any message') + ); + + $rssModel->expects($this->once())->method('createRssXml')->will( + $this->throwException($exceptionMock) + ); + $this->response->expects($this->once())->method('setHeader')->will($this->returnSelf()); $this->rssFactory->expects($this->once())->method('create')->will($this->returnValue($rssModel)); $this->rssManager->expects($this->once())->method('getProvider')->will($this->returnValue($dataProvider)); - $this->expectException(InvalidArgumentException::class); + $this->expectException(\Magento\Framework\Exception\RuntimeException::class); $this->controller->execute(); } } diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php index e8f7a85382f27..30415155d5f6e 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php @@ -53,6 +53,7 @@ protected function setUp() ->disableOriginalConstructor()->getMock(); $objectManagerHelper = new ObjectManagerHelper($this); + $this->controller = $objectManagerHelper->getObject( \Magento\Rss\Controller\Feed\Index::class, [ @@ -91,14 +92,22 @@ public function testExecuteWithException() $dataProvider = $this->createMock(\Magento\Framework\App\Rss\DataProviderInterface::class); $dataProvider->expects($this->once())->method('isAllowed')->will($this->returnValue(true)); - $rssModel = $this->createPartialMock(\Magento\Rss\Model\Rss::class, ['setDataProvider']); + $rssModel = $this->createPartialMock(\Magento\Rss\Model\Rss::class, ['setDataProvider', 'createRssXml']); $rssModel->expects($this->once())->method('setDataProvider')->will($this->returnSelf()); + $exceptionMock = new \Magento\Framework\Exception\RuntimeException( + new \Magento\Framework\Phrase('Any message') + ); + + $rssModel->expects($this->once())->method('createRssXml')->will( + $this->throwException($exceptionMock) + ); + $this->response->expects($this->once())->method('setHeader')->will($this->returnSelf()); $this->rssFactory->expects($this->once())->method('create')->will($this->returnValue($rssModel)); $this->rssManager->expects($this->once())->method('getProvider')->will($this->returnValue($dataProvider)); - $this->expectException(InvalidArgumentException::class); + $this->expectException(\Magento\Framework\Exception\RuntimeException::class); $this->controller->execute(); } } diff --git a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php index 34f9940556a93..f2888e4296b40 100644 --- a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php +++ b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php @@ -19,7 +19,7 @@ class RssTest extends \PHPUnit\Framework\TestCase /** * @var array */ - protected $feedData = [ + private $feedData = [ 'title' => 'Feed Title', 'link' => 'http://magento.com/rss/link', 'description' => 'Feed Description', @@ -33,6 +33,27 @@ class RssTest extends \PHPUnit\Framework\TestCase ], ]; + /** + * @var string + */ + private $feedXml = ' + + + + http://magento.com/rss/link + + Sat, 22 Apr 2017 13:21:12 +0200 + Zend\Feed + http://blogs.law.harvard.edu/tech/rss + + + http://magento.com/rss/link/id/1 + + Sat, 22 Apr 2017 13:21:12 +0200 + + +'; + /** * @var ObjectManagerHelper */ @@ -43,6 +64,16 @@ class RssTest extends \PHPUnit\Framework\TestCase */ private $cacheMock; + /** + * @var \Magento\Framework\App\FeedFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $feedFactoryMock; + + /** + * @var \Magento\Framework\App\FeedInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $feedMock; + /** * @var SerializerInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -52,11 +83,15 @@ protected function setUp() { $this->cacheMock = $this->createMock(\Magento\Framework\App\CacheInterface::class); $this->serializerMock = $this->createMock(SerializerInterface::class); + $this->feedFactoryMock = $this->createMock(\Magento\Framework\App\FeedFactoryInterface::class); + $this->feedMock = $this->createMock(\Magento\Framework\App\FeedInterface::class); + $this->objectManagerHelper = new ObjectManagerHelper($this); $this->rss = $this->objectManagerHelper->getObject( \Magento\Rss\Model\Rss::class, [ 'cache' => $this->cacheMock, + 'feedFactory' => $this->feedFactoryMock, 'serializer' => $this->serializerMock ] ); @@ -116,14 +151,16 @@ public function testCreateRssXml() $dataProvider->expects($this->any())->method('getCacheLifetime')->will($this->returnValue(100)); $dataProvider->expects($this->any())->method('getRssData')->will($this->returnValue($this->feedData)); + $this->feedMock->expects($this->once()) + ->method('getFormattedContent') + ->willReturn($this->feedXml); + + $this->feedFactoryMock->expects($this->once()) + ->method('create') + ->with($this->feedData, \Magento\Framework\App\FeedFactoryInterface::FORMAT_RSS) + ->will($this->returnValue($this->feedMock)); + $this->rss->setDataProvider($dataProvider); - $result = $this->rss->createRssXml(); - $this->assertContains('', $result); - $this->assertContains('Feed Title', $result); - $this->assertContains('Feed 1 Title', $result); - $this->assertContains('http://magento.com/rss/link', $result); - $this->assertContains('http://magento.com/rss/link/id/1', $result); - $this->assertContains('Feed Description', $result); - $this->assertContains('', $result); + $this->assertNotNull($this->rss->createRssXml()); } } diff --git a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php index 01e715f06a27f..e3bec2d9959b1 100644 --- a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php +++ b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php @@ -24,7 +24,6 @@ abstract class AbstractCondition extends \Magento\Framework\DataObject implement { /** * Defines which operators will be available for this condition - * * @var string */ protected $_inputType = null; @@ -84,17 +83,13 @@ public function __construct(Context $context, array $data = []) $options = $this->getAttributeOptions(); if ($options) { - foreach (array_keys($options) as $attr) { - $this->setAttribute($attr); - break; - } + reset($options); + $this->setAttribute(key($options)); } $options = $this->getOperatorOptions(); if ($options) { - foreach (array_keys($options) as $operator) { - $this->setOperator($operator); - break; - } + reset($options); + $this->setOperator(key($options)); } } @@ -160,14 +155,13 @@ public function getForm() */ public function asArray(array $arrAttributes = []) { - $out = [ + return [ 'type' => $this->getType(), 'attribute' => $this->getAttribute(), 'operator' => $this->getOperator(), 'value' => $this->getValue(), 'is_value_processed' => $this->getIsValueParsed(), ]; - return $out; } /** @@ -205,7 +199,7 @@ public function getMappedSqlField() */ public function asXml() { - $xml = "" . + return "" . $this->getType() . "" . "" . @@ -217,7 +211,6 @@ public function asXml() "" . $this->getValue() . ""; - return $xml; } /** @@ -244,8 +237,7 @@ public function loadXml($xml) if (is_string($xml)) { $xml = simplexml_load_string($xml); } - $arr = (array)$xml; - $this->loadArray($arr); + $this->loadArray((array)$xml); return $this; } @@ -304,10 +296,7 @@ public function loadOperatorOptions() */ public function getInputType() { - if (null === $this->_inputType) { - return 'string'; - } - return $this->_inputType; + return null === $this->_inputType ? 'string' : $this->_inputType; } /** @@ -348,12 +337,11 @@ public function loadValueOptions() */ public function getValueSelectOptions() { - $valueOption = $opt = []; + $opt = []; if ($this->hasValueOption()) { - $valueOption = (array)$this->getValueOption(); - } - foreach ($valueOption as $key => $value) { - $opt[] = ['value' => $key, 'label' => $value]; + foreach ((array)$this->getValueOption() as $key => $value) { + $opt[] = ['value' => $key, 'label' => $value]; + } } return $opt; } @@ -470,13 +458,12 @@ public function getNewChildName() */ public function asHtml() { - $html = $this->getTypeElementHtml() . + return $this->getTypeElementHtml() . $this->getAttributeElementHtml() . $this->getOperatorElementHtml() . $this->getValueElementHtml() . $this->getRemoveLinkHtml() . $this->getChooserContainerHtml(); - return $html; } /** @@ -484,8 +471,7 @@ public function asHtml() */ public function asHtmlRecursive() { - $html = $this->asHtml(); - return $html; + return $this->asHtml(); } /** @@ -520,9 +506,10 @@ public function getTypeElementHtml() public function getAttributeElement() { if (null === $this->getAttribute()) { - foreach (array_keys($this->getAttributeOption()) as $option) { - $this->setAttribute($option); - break; + $options = $this->getAttributeOption(); + if ($options) { + reset($options); + $this->setAttribute(key($options)); } } return $this->getForm()->addField( @@ -558,10 +545,8 @@ public function getOperatorElement() { $options = $this->getOperatorSelectOptions(); if ($this->getOperator() === null) { - foreach ($options as $option) { - $this->setOperator($option['value']); - break; - } + $option = reset($options); + $this->setOperator($option['value']); } $elementId = sprintf('%s__%s__operator', $this->getPrefix(), $this->getId()); @@ -654,8 +639,7 @@ public function getValueElementHtml() public function getAddLinkHtml() { $src = $this->_assetRepo->getUrl('images/rule_component_add.gif'); - $html = ''; - return $html; + return ''; } /** @@ -676,11 +660,7 @@ public function getRemoveLinkHtml() public function getChooserContainerHtml() { $url = $this->getValueElementChooserUrl(); - $html = ''; - if ($url) { - $html = ''; - } - return $html; + return $url ? '' : ''; } /** @@ -690,8 +670,7 @@ public function getChooserContainerHtml() */ public function asString($format = '') { - $str = $this->getAttributeName() . ' ' . $this->getOperatorName() . ' ' . $this->getValueName(); - return $str; + return $this->getAttributeName() . ' ' . $this->getOperatorName() . ' ' . $this->getValueName(); } /** @@ -700,8 +679,7 @@ public function asString($format = '') */ public function asStringRecursive($level = 0) { - $str = str_pad('', $level * 3, ' ', STR_PAD_LEFT) . $this->asString(); - return $str; + return str_pad('', $level * 3, ' ', STR_PAD_LEFT) . $this->asString(); } /** @@ -740,12 +718,10 @@ public function validateAttribute($validatedValue) case '==': case '!=': if (is_array($value)) { - if (is_array($validatedValue)) { - $result = array_intersect($value, $validatedValue); - $result = !empty($result); - } else { + if (!is_array($validatedValue)) { return false; } + $result = !empty(array_intersect($value, $validatedValue)); } else { if (is_array($validatedValue)) { $result = count($validatedValue) == 1 && array_shift($validatedValue) == $value; @@ -759,18 +735,16 @@ public function validateAttribute($validatedValue) case '>': if (!is_scalar($validatedValue)) { return false; - } else { - $result = $validatedValue <= $value; } + $result = $validatedValue <= $value; break; case '>=': case '<': if (!is_scalar($validatedValue)) { return false; - } else { - $result = $validatedValue >= $value; } + $result = $validatedValue >= $value; break; case '{}': @@ -783,12 +757,11 @@ public function validateAttribute($validatedValue) } } } elseif (is_array($value)) { - if (is_array($validatedValue)) { - $result = array_intersect($value, $validatedValue); - $result = !empty($result); - } else { + if (!is_array($validatedValue)) { return false; } + $result = array_intersect($value, $validatedValue); + $result = !empty($result); } else { if (is_array($validatedValue)) { $result = in_array($value, $validatedValue); @@ -833,13 +806,13 @@ protected function _compareValues($validatedValue, $value, $strict = true) { if ($strict && is_numeric($validatedValue) && is_numeric($value)) { return $validatedValue == $value; - } else { - $validatePattern = preg_quote($validatedValue, '~'); - if ($strict) { - $validatePattern = '^' . $validatePattern . '$'; - } - return (bool)preg_match('~' . $validatePattern . '~iu', $value); } + + $validatePattern = preg_quote($validatedValue, '~'); + if ($strict) { + $validatePattern = '^' . $validatePattern . '$'; + } + return (bool)preg_match('~' . $validatePattern . '~iu', $value); } /** diff --git a/app/code/Magento/Rule/view/adminhtml/web/rules.js b/app/code/Magento/Rule/view/adminhtml/web/rules.js index 5c4be367b9cb3..b094b9818364a 100644 --- a/app/code/Magento/Rule/view/adminhtml/web/rules.js +++ b/app/code/Magento/Rule/view/adminhtml/web/rules.js @@ -137,7 +137,7 @@ define([ }, onSuccess: function (transport) { if (this._processSuccess(transport)) { - $(chooser).update(transport.responseText); + jQuery(chooser).html(transport.responseText); this.showChooserLoaded(chooser, transport); jQuery(chooser).trigger('contentUpdated'); } diff --git a/app/code/Magento/Sales/Model/Order/ItemRepository.php b/app/code/Magento/Sales/Model/Order/ItemRepository.php index f1bbb7d39469b..7916eb9db2b80 100644 --- a/app/code/Magento/Sales/Model/Order/ItemRepository.php +++ b/app/code/Magento/Sales/Model/Order/ItemRepository.php @@ -117,6 +117,7 @@ public function get($id) } $this->addProductOption($orderItem); + $this->addParentItem($orderItem); $this->registry[$id] = $orderItem; } return $this->registry[$id]; @@ -216,6 +217,20 @@ protected function addProductOption(OrderItemInterface $orderItem) return $this; } + /** + * Set parent item. + * + * @param OrderItemInterface $orderItem + * @throws InputException + * @throws NoSuchEntityException + */ + private function addParentItem(OrderItemInterface $orderItem) + { + if ($parentId = $orderItem->getParentItemId()) { + $orderItem->setParentItem($this->get($parentId)); + } + } + /** * Set product options data * diff --git a/app/code/Magento/Sales/Model/OrderIncrementIdChecker.php b/app/code/Magento/Sales/Model/OrderIncrementIdChecker.php new file mode 100644 index 0000000000000..0af61835a9c9b --- /dev/null +++ b/app/code/Magento/Sales/Model/OrderIncrementIdChecker.php @@ -0,0 +1,52 @@ +resourceModel = $resourceModel; + } + + /** + * Check if order increment ID is already used. + * + * Method can be used to avoid collisions of order IDs. + * + * @param string|int $orderIncrementId + * @return bool + */ + public function isIncrementIdUsed($orderIncrementId): bool + { + /** @var \Magento\Framework\DB\Adapter\AdapterInterface $adapter */ + $adapter = $this->resourceModel->getConnection(); + $bind = [':increment_id' => $orderIncrementId]; + /** @var \Magento\Framework\DB\Select $select */ + $select = $adapter->select(); + $select->from($this->resourceModel->getMainTable(), $this->resourceModel->getIdFieldName()) + ->where('increment_id = :increment_id'); + $entity_id = $adapter->fetchOne($select, $bind); + if ($entity_id > 0) { + return true; + } + + return false; + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderIncrementIdCheckerTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderIncrementIdCheckerTest.php new file mode 100644 index 0000000000000..e53cb7bfdf8c6 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/OrderIncrementIdCheckerTest.php @@ -0,0 +1,81 @@ +selectMock = $this->createMock(\Magento\Framework\DB\Select::class); + $this->selectMock->expects($this->any())->method('from')->will($this->returnSelf()); + $this->selectMock->expects($this->any())->method('where'); + + $this->adapterMock = $this->createMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class); + $this->adapterMock->expects($this->any())->method('select')->will($this->returnValue($this->selectMock)); + + $this->resourceMock = $this->createMock(\Magento\Sales\Model\ResourceModel\Order::class); + $this->resourceMock->expects($this->any())->method('getConnection')->willReturn($this->adapterMock); + + $this->model = $objectManagerHelper->getObject( + \Magento\Sales\Model\OrderIncrementIdChecker::class, + [ + 'resourceModel' => $this->resourceMock, + ] + ); + } + + /** + * Unit test to verify if isOrderIncrementIdUsed method works with different types increment ids. + * + * @param string|int $value + * @return void + * @dataProvider isOrderIncrementIdUsedDataProvider + */ + public function testIsIncrementIdUsed($value): void + { + $expectedBind = [':increment_id' => $value]; + $this->adapterMock->expects($this->once())->method('fetchOne')->with($this->selectMock, $expectedBind); + $this->model->isIncrementIdUsed($value); + } + + /** + * @return array + */ + public function isOrderIncrementIdUsedDataProvider(): array + { + return [[100000001], ['10000000001'], ['M10000000001']]; + } +} diff --git a/app/code/Magento/Sales/etc/db_schema.xml b/app/code/Magento/Sales/etc/db_schema.xml index 8c91a8b5fbc13..4b716e761094c 100644 --- a/app/code/Magento/Sales/etc/db_schema.xml +++ b/app/code/Magento/Sales/etc/db_schema.xml @@ -216,7 +216,7 @@ - + diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php new file mode 100644 index 0000000000000..f413a7d047d62 --- /dev/null +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php @@ -0,0 +1,26 @@ +getActualLength(); $code = ''; for ($i = 0, $indexMax = strlen($alphabet) - 1; $i < $length; ++$i) { - $code .= substr($alphabet, mt_rand(0, $indexMax), 1); + $code .= substr($alphabet, random_int(0, $indexMax), 1); } return $code; @@ -54,7 +54,7 @@ protected function getActualLength() $lengthMin = $this->getLengthMin() ? $this->getLengthMin() : static::DEFAULT_LENGTH_MIN; $lengthMax = $this->getLengthMax() ? $this->getLengthMax() : static::DEFAULT_LENGTH_MAX; - return $this->getLength() ? $this->getLength() : mt_rand($lengthMin, $lengthMax); + return $this->getLength() ? $this->getLength() : random_int($lengthMin, $lengthMax); } /** diff --git a/app/code/Magento/SalesRule/Model/DeltaPriceRound.php b/app/code/Magento/SalesRule/Model/DeltaPriceRound.php new file mode 100644 index 0000000000000..b080a93ee4c9f --- /dev/null +++ b/app/code/Magento/SalesRule/Model/DeltaPriceRound.php @@ -0,0 +1,78 @@ +priceCurrency = $priceCurrency; + } + + /** + * Round price based on previous rounding operation delta. + * + * @param float $price + * @param string $type + * @return float + */ + public function round(float $price, string $type): float + { + if ($price) { + // initialize the delta to a small number to avoid non-deterministic behavior with rounding of 0.5 + $delta = isset($this->roundingDeltas[$type]) ? $this->roundingDeltas[$type] : 0.000001; + $price += $delta; + $roundPrice = $this->priceCurrency->round($price); + $this->roundingDeltas[$type] = $price - $roundPrice; + $price = $roundPrice; + } + + return $price; + } + + /** + * Reset all deltas. + * + * @return void + */ + public function resetAll(): void + { + $this->roundingDeltas = []; + } + + /** + * Reset deltas by type. + * + * @param string $type + * @return void + */ + public function reset(string $type): void + { + if (isset($this->roundingDeltas[$type])) { + unset($this->roundingDeltas[$type]); + } + } +} diff --git a/app/code/Magento/SalesRule/Model/Rule.php b/app/code/Magento/SalesRule/Model/Rule.php index f4469213bd96e..59efdf5eb3f6d 100644 --- a/app/code/Magento/SalesRule/Model/Rule.php +++ b/app/code/Magento/SalesRule/Model/Rule.php @@ -521,7 +521,7 @@ public function acquireCoupon($saveNewlyCreated = true, $saveAttemptCount = 10) $coupon->setCode( $couponCode . self::getCouponCodeGenerator()->getDelimiter() . sprintf( '%04u', - rand(0, 9999) + random_int(0, 9999) ) ); continue; diff --git a/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php b/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php index caa938322617d..3cd776fe99f5d 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php +++ b/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php @@ -5,6 +5,14 @@ */ namespace Magento\SalesRule\Model\Rule\Action\Discount; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\SalesRule\Model\DeltaPriceRound; +use Magento\SalesRule\Model\Validator; + +/** + * Calculates discount for cart item if fixed discount applied on whole cart. + */ class CartFixed extends AbstractDiscount { /** @@ -14,6 +22,33 @@ class CartFixed extends AbstractDiscount */ protected $_cartFixedRuleUsedForAddress = []; + /** + * @var DeltaPriceRound + */ + private $deltaPriceRound; + + /** + * @var string + */ + private static $discountType = 'CartFixed'; + + /** + * @param Validator $validator + * @param DataFactory $discountDataFactory + * @param PriceCurrencyInterface $priceCurrency + * @param DeltaPriceRound $deltaPriceRound + */ + public function __construct( + Validator $validator, + DataFactory $discountDataFactory, + PriceCurrencyInterface $priceCurrency, + DeltaPriceRound $deltaPriceRound + ) { + $this->deltaPriceRound = $deltaPriceRound; + + parent::__construct($validator, $discountDataFactory, $priceCurrency); + } + /** * @param \Magento\SalesRule\Model\Rule $rule * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item @@ -51,14 +86,22 @@ public function calculate($rule, $item, $qty) $cartRules[$rule->getId()] = $rule->getDiscountAmount(); } - if ($cartRules[$rule->getId()] > 0) { + $availableDiscountAmount = (float)$cartRules[$rule->getId()]; + $discountType = self::$discountType . $rule->getId(); + + if ($availableDiscountAmount > 0) { $store = $quote->getStore(); if ($ruleTotals['items_count'] <= 1) { - $quoteAmount = $this->priceCurrency->convert($cartRules[$rule->getId()], $store); - $baseDiscountAmount = min($baseItemPrice * $qty, $cartRules[$rule->getId()]); + $quoteAmount = $this->priceCurrency->convert($availableDiscountAmount, $store); + $baseDiscountAmount = min($baseItemPrice * $qty, $availableDiscountAmount); + $this->deltaPriceRound->reset($discountType); } else { - $discountRate = $baseItemPrice * $qty / $ruleTotals['base_items_price']; - $maximumItemDiscount = $rule->getDiscountAmount() * $discountRate; + $ratio = $baseItemPrice * $qty / $ruleTotals['base_items_price']; + $maximumItemDiscount = $this->deltaPriceRound->round( + $rule->getDiscountAmount() * $ratio, + $discountType + ); + $quoteAmount = $this->priceCurrency->convert($maximumItemDiscount, $store); $baseDiscountAmount = min($baseItemPrice * $qty, $maximumItemDiscount); @@ -67,7 +110,11 @@ public function calculate($rule, $item, $qty) $baseDiscountAmount = $this->priceCurrency->round($baseDiscountAmount); - $cartRules[$rule->getId()] -= $baseDiscountAmount; + $availableDiscountAmount -= $baseDiscountAmount; + $cartRules[$rule->getId()] = $availableDiscountAmount; + if ($availableDiscountAmount <= 0) { + $this->deltaPriceRound->reset($discountType); + } $discountData->setAmount($this->priceCurrency->round(min($itemPrice * $qty, $quoteAmount))); $discountData->setBaseAmount($baseDiscountAmount); diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/DeltaPriceRoundTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/DeltaPriceRoundTest.php new file mode 100644 index 0000000000000..d67dab5baf63b --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Unit/Model/DeltaPriceRoundTest.php @@ -0,0 +1,102 @@ +priceCurrency = $this->getMockForAbstractClass(PriceCurrencyInterface::class); + $this->priceCurrency->method('round') + ->willReturnCallback( + function ($amount) { + return round($amount, 2); + } + ); + + $this->model = new DeltaPriceRound($this->priceCurrency); + } + + /** + * Tests rounded price based on previous rounding operation delta. + * + * @param array $prices + * @param array $roundedPrices + * @return void + * @dataProvider roundDataProvider + */ + public function testRound(array $prices, array $roundedPrices): void + { + foreach ($prices as $key => $price) { + $roundedPrice = $this->model->round($price, 'test'); + $this->assertEquals($roundedPrices[$key], $roundedPrice); + } + + $this->model->reset('test'); + } + + /** + * @return array + */ + public function roundDataProvider(): array + { + return [ + [ + 'prices' => [1.004, 1.004], + 'rounded prices' => [1.00, 1.01], + ], + [ + 'prices' => [1.005, 1.005], + 'rounded prices' => [1.01, 1.0], + ], + ]; + } + + /** + * @return void + */ + public function testReset(): void + { + $this->assertEquals(1.44, $this->model->round(1.444, 'test')); + $this->model->reset('test'); + $this->assertEquals(1.44, $this->model->round(1.444, 'test')); + } + + /** + * @return void + */ + public function testResetAll(): void + { + $this->assertEquals(1.44, $this->model->round(1.444, 'test1')); + $this->assertEquals(1.44, $this->model->round(1.444, 'test2')); + + $this->model->resetAll(); + + $this->assertEquals(1.44, $this->model->round(1.444, 'test1')); + $this->assertEquals(1.44, $this->model->round(1.444, 'test2')); + } +} diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Action/Discount/CartFixedTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Action/Discount/CartFixedTest.php index 2f1bee9fc686a..13f26124c464d 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Action/Discount/CartFixedTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Action/Discount/CartFixedTest.php @@ -5,48 +5,56 @@ */ namespace Magento\SalesRule\Test\Unit\Model\Rule\Action\Discount; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * Tests for Magento\SalesRule\Model\Rule\Action\Discount\CartFixed. + */ class CartFixedTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\SalesRule\Model\Rule|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\SalesRule\Model\Rule|MockObject */ protected $rule; /** - * @var \Magento\Quote\Model\Quote\Item\AbstractItem|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Quote\Model\Quote\Item\AbstractItem|MockObject */ protected $item; /** - * @var \Magento\SalesRule\Model\Validator|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\SalesRule\Model\Validator|MockObject */ protected $validator; /** - * @var \Magento\SalesRule\Model\Rule\Action\Discount\Data|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\SalesRule\Model\Rule\Action\Discount\Data|MockObject */ protected $data; /** - * @var \Magento\Quote\Model\Quote|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Quote\Model\Quote|MockObject */ protected $quote; /** - * @var \Magento\Quote\Model\Quote\Address|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Quote\Model\Quote\Address|MockObject */ protected $address; /** - * @var CartFixed + * @var \Magento\SalesRule\Model\Rule\Action\Discount\CartFixed */ protected $model; /** - * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Pricing\PriceCurrencyInterface|MockObject */ protected $priceCurrency; + /** + * @inheritdoc + */ protected function setUp() { $this->rule = $this->getMockBuilder(\Magento\Framework\DataObject::class) @@ -66,18 +74,23 @@ protected function setUp() $this->item->expects($this->any())->method('getAddress')->will($this->returnValue($this->address)); $this->validator = $this->createMock(\Magento\SalesRule\Model\Validator::class); + /** @var \Magento\SalesRule\Model\Rule\Action\Discount\DataFactory|MockObject $dataFactory */ $dataFactory = $this->createPartialMock( \Magento\SalesRule\Model\Rule\Action\Discount\DataFactory::class, ['create'] ); $dataFactory->expects($this->any())->method('create')->will($this->returnValue($this->data)); - $this->priceCurrency = $this->getMockBuilder( - \Magento\Framework\Pricing\PriceCurrencyInterface::class - )->getMock(); + $this->priceCurrency = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class) + ->getMock(); + $deltaPriceRound = $this->getMockBuilder(\Magento\SalesRule\Model\DeltaPriceRound::class) + ->disableOriginalConstructor() + ->getMock(); + $this->model = new \Magento\SalesRule\Model\Rule\Action\Discount\CartFixed( $this->validator, $dataFactory, - $this->priceCurrency + $this->priceCurrency, + $deltaPriceRound ); } diff --git a/app/code/Magento/Swatches/view/adminhtml/ui_component/product_attribute_add_form.xml b/app/code/Magento/Swatches/view/adminhtml/ui_component/product_attribute_add_form.xml index 4152c06fa3ddc..2fdf5f3cd0ea9 100644 --- a/app/code/Magento/Swatches/view/adminhtml/ui_component/product_attribute_add_form.xml +++ b/app/code/Magento/Swatches/view/adminhtml/ui_component/product_attribute_add_form.xml @@ -87,7 +87,6 @@ true - text_swatch dynamicRows @@ -96,8 +95,6 @@ true true container - text_swatch.position - @@ -183,15 +180,11 @@ - - true - text false - position - + true @@ -227,7 +220,6 @@ true true - visual_swatch dynamicRows @@ -236,8 +228,6 @@ true true container - text_swatch.position - @@ -276,7 +266,6 @@ true Swatch - swatchvisual @@ -290,7 +279,6 @@ text Default Store View - optionvisual_default_store_view @@ -304,7 +292,6 @@ text Admin - optionvisual_admin @@ -315,15 +302,10 @@ - - true - - text - false - position + false - + true diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/form/element/swatch-visual.js b/app/code/Magento/Swatches/view/adminhtml/web/js/form/element/swatch-visual.js index e63c9a2138a36..2fbce5aefbdeb 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/js/form/element/swatch-visual.js +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/form/element/swatch-visual.js @@ -305,18 +305,30 @@ define([ */ initialize: function () { this._super() - .initOldCode(); + .initOldCode() + .on('value', this.onChangeColor.bind(this)); return this; }, + /** + * Handler function that execute when color changes. + * + * @param {String} data - color + */ + onChangeColor: function (data) { + if (!data) { + jQuery('.' + this.elementName).parent().removeClass('unavailable'); + } + }, + /** * Initialize wrapped former implementation. * * @returns {Object} Chainable. */ initOldCode: function () { - jQuery.async('.' + this.elementName, function (elem) { + jQuery.async('.' + this.elementName, this.name, function (elem) { oldCode(this.value(), elem.parentElement, this.uploadUrl, this.elementName); }.bind(this)); @@ -336,9 +348,15 @@ define([ this.elementName = this.prefixElementName + recordId; this.inputName = prefixName + '[' + this.elementName + ']'; - this.dataScope = 'data.' + this.prefixName + '.' + this.elementName; + this.exportDataLink = 'data.' + this.prefixName + '.' + this.elementName; + this.exports.value = this.provider + ':' + this.exportDataLink; + }, + + /** @inheritdoc */ + destroy: function () { + this._super(); - this.links.value = this.provider + ':' + this.dataScope; + this.source.remove(this.exportDataLink); }, /** diff --git a/app/code/Magento/Swatches/view/adminhtml/web/template/swatch-visual.html b/app/code/Magento/Swatches/view/adminhtml/web/template/swatch-visual.html index 271cea918b7be..8296e944a8ed3 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/template/swatch-visual.html +++ b/app/code/Magento/Swatches/view/adminhtml/web/template/swatch-visual.html @@ -11,7 +11,7 @@ }, value: value "/> - + diff --git a/app/code/Magento/Tax/i18n/en_US.csv b/app/code/Magento/Tax/i18n/en_US.csv index 2314f27b92928..e6d89deb7696c 100644 --- a/app/code/Magento/Tax/i18n/en_US.csv +++ b/app/code/Magento/Tax/i18n/en_US.csv @@ -176,4 +176,5 @@ Rate,Rate "Order Total Incl. Tax","Order Total Incl. Tax" "Order Total","Order Total" "Your credit card will be charged for","Your credit card will be charged for" -"An error occurred while loading tax rates.","An error occurred while loading tax rates." \ No newline at end of file +"An error occurred while loading tax rates.","An error occurred while loading tax rates." +"You will be charged for","You will be charged for" diff --git a/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml index 41afddb8fa4e1..8f60ba15d8a1a 100644 --- a/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml @@ -70,7 +70,7 @@ Order Total Excl. Tax Order Total Incl. Tax - Your credit card will be charged for + You will be charged for Order Total diff --git a/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js b/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js index fd5e9628fecdb..474861a523878 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js +++ b/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js @@ -480,7 +480,9 @@ define([ * @param {String} directive */ makeDirectiveUrl: function (directive) { - return this.config['directives_url'].replace(/directive.*/, 'directive/___directive/' + directive); + return this.config['directives_url'] + .replace(/directive/, 'directive/___directive/' + directive) + .replace(/\/$/, ''); }, /** @@ -537,12 +539,18 @@ define([ * @return {*} */ decodeDirectives: function (content) { - // escape special chars in directives url to use it in regular expression - var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), - reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9%,_-]+)\/?')); - - return content.gsub(reg, function (match) { //eslint-disable-line no-extra-bind - return Base64.mageDecode(decodeURIComponent(match[1])).replace(/"/g, '"'); + var directiveUrl = this.makeDirectiveUrl('%directive%').split('?')[0], // remove query string from directive + // escape special chars in directives url to use in regular expression + regexEscapedDirectiveUrl = directiveUrl.replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), + regexDirectiveUrl = regexEscapedDirectiveUrl + .replace( + '%directive%', + '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)(?:(?!").)*' + ) + '/?(\\\\?[^"]*)?', // allow optional query string + reg = new RegExp(regexDirectiveUrl); + + return content.gsub(reg, function (match) { + return Base64.mageDecode(decodeURIComponent(match[1]).replace(/\/$/, '')).replace(/"/g, '"'); }); }, diff --git a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php index 691c6bfdd420c..aee81f65775bc 100644 --- a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php +++ b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php @@ -64,7 +64,10 @@ public function getComponentName() public function prepare() { // dynamically set max file size based on php ini config if not present in XML - $maxFileSize = $this->getConfiguration()['maxFileSize'] ?? $this->fileSize->getMaxFileSize(); + $maxFileSize = min(array_filter([ + $this->getConfiguration()['maxFileSize'] ?? 0, + $this->fileSize->getMaxFileSize() + ])); $data = array_replace_recursive( $this->getData(), diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/Media/ImageTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/Media/ImageTest.php new file mode 100644 index 0000000000000..ebe4d10475cc9 --- /dev/null +++ b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/Media/ImageTest.php @@ -0,0 +1,127 @@ +processor = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\Processor::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->context->expects($this->atLeastOnce())->method('getProcessor')->willReturn($this->processor); + + $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); + + $this->store = $this->getMockBuilder(StoreInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->store->expects($this->any())->method('getId')->willReturn(0); + + $this->storeManager->expects($this->any())->method('getStore')->willReturn($this->store); + + $this->fileSize = $this->getMockBuilder(Size::class)->getMock(); + + $this->objectManager = new ObjectManager($this); + + $this->image = $this->objectManager->getObject(Image::class, [ + 'context' => $this->context, + 'storeManager' => $this->storeManager, + 'fileSize' => $this->fileSize + ]); + + $this->image->setData([ + 'config' => [ + 'initialMediaGalleryOpenSubpath' => 'open/sesame', + ], + ]); + } + + /** + * @dataProvider prepareDataProvider + */ + public function testPrepare() + { + $this->assertExpectedPreparedConfiguration(...func_get_args()); + } + + /** + * Data provider for testPrepare + * @return array + */ + public function prepareDataProvider(): array + { + return [ + [['maxFileSize' => 10], 10, ['maxFileSize' => 10]], + [['maxFileSize' => null], 10, ['maxFileSize' => 10]], + [['maxFileSize' => 10], 5, ['maxFileSize' => 5]], + [['maxFileSize' => 10], 20, ['maxFileSize' => 10]], + [['maxFileSize' => 0], 10, ['maxFileSize' => 10]], + ]; + } + + /** + * @param array $initialConfig + * @param int $maxFileSizeSupported + * @param array $expectedPreparedConfig + */ + private function assertExpectedPreparedConfiguration( + array $initialConfig, + int $maxFileSizeSupported, + array $expectedPreparedConfig + ) { + $this->image->setData(array_merge_recursive(['config' => $initialConfig], $this->image->getData())); + + $this->fileSize->expects($this->any())->method('getMaxFileSize')->willReturn($maxFileSizeSupported); + + $this->image->prepare(); + + $actualRelevantPreparedConfig = array_intersect_key($this->image->getConfiguration(), $initialConfig); + + $this->assertEquals( + $expectedPreparedConfig, + $actualRelevantPreparedConfig + ); + } +} diff --git a/app/code/Magento/Ui/etc/di.xml b/app/code/Magento/Ui/etc/di.xml index 8cf0e1e12e781..c029e18addf73 100644 --- a/app/code/Magento/Ui/etc/di.xml +++ b/app/code/Magento/Ui/etc/di.xml @@ -252,6 +252,7 @@ + Magento\Framework\Data\Argument\Interpreter\Constant configurableObjectArgumentInterpreterProxy configurableObjectArgumentInterpreterProxy arrayArgumentInterpreterProxy diff --git a/app/code/Magento/Ui/etc/ui_configuration.xsd b/app/code/Magento/Ui/etc/ui_configuration.xsd index 6847f226dd609..b839593a38f47 100644 --- a/app/code/Magento/Ui/etc/ui_configuration.xsd +++ b/app/code/Magento/Ui/etc/ui_configuration.xsd @@ -209,6 +209,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -287,7 +311,7 @@ - + diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/boolean.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/boolean.xsd index 7cc33cf4351df..21ca445a4177b 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/boolean.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/boolean.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + @@ -25,7 +39,6 @@ - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/button.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/button.xsd index f4b4dd403509f..3c9866e6845e7 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/button.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/button.xsd @@ -23,6 +23,19 @@ + + + + + + + + + + + + + diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/checkbox.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/checkbox.xsd index 13bc629606470..4dfb074498e5b 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/checkbox.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/checkbox.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + @@ -25,7 +39,6 @@ - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/checkboxset.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/checkboxset.xsd index b0dea52cb4bd9..8fa509fa594d6 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/checkboxset.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/checkboxset.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + @@ -25,7 +39,6 @@ - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/colorPicker.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/colorPicker.xsd index 509ef2aad2dee..dea1f7ce059e9 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/colorPicker.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/colorPicker.xsd @@ -22,6 +22,19 @@ + + + + + + + + + + + + + diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/date.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/date.xsd index b24ba626a9ea1..a2a8fbbe103c1 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/date.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/date.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + @@ -25,7 +39,6 @@ - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/email.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/email.xsd index cd1147b560034..648eaf9db5b18 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/email.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/email.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + @@ -25,7 +39,6 @@ - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/file.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/file.xsd index eac284ddc92d8..1536f752a0aaf 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/file.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/file.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + @@ -25,7 +39,6 @@ - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/fileUploader.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/fileUploader.xsd index 3181a7ab673cd..664a2d0e8d904 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/fileUploader.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/fileUploader.xsd @@ -23,6 +23,19 @@ + + + + + + + + + + + + + diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/hidden.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/hidden.xsd index 82282dd61673f..0e7a6f4ed5fe0 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/hidden.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/hidden.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + @@ -25,7 +39,6 @@ - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/imageUploader.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/imageUploader.xsd index 6bb29ba2e9069..f32d5c2c0fe8f 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/imageUploader.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/imageUploader.xsd @@ -23,6 +23,19 @@ + + + + + + + + + + + + + diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/input.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/input.xsd index fc3ea2d92576a..047212f3973e4 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/input.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/input.xsd @@ -15,6 +15,7 @@ + @@ -23,6 +24,18 @@ + + + + + + + + + + + + @@ -73,7 +86,6 @@ - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/multiselect.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/multiselect.xsd index 28d9d24868820..aefb0328c8800 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/multiselect.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/multiselect.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/price.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/price.xsd index 5a3053e59be95..69b936c3e5f8a 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/price.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/price.xsd @@ -12,7 +12,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/radioset.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/radioset.xsd index c53ad0333d6d7..f2db9a57c5a1c 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/radioset.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/radioset.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + @@ -25,7 +39,6 @@ - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/select.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/select.xsd index 7e0d2dfbfeb18..76d37de771a4c 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/select.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/select.xsd @@ -15,6 +15,7 @@ + @@ -29,9 +30,21 @@ + + + + + + + + + + + + + - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/text.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/text.xsd index e382af84111c7..813ad7870bfe9 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/text.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/text.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + @@ -25,7 +39,6 @@ - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/textarea.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/textarea.xsd index ad7fda6200a79..67ee63a3f889f 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/textarea.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/textarea.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + @@ -25,7 +39,6 @@ - diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/wysiwyg.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/wysiwyg.xsd index 62c81234aba33..3e7bbb3a59f8c 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/wysiwyg.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/wysiwyg.xsd @@ -12,6 +12,20 @@ + + + + + + + + + + + + + + @@ -25,7 +39,6 @@ - diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js index 635c78ce65fe8..680b6a2b1e54e 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js @@ -224,6 +224,14 @@ define([ return this; }, + /** @inheritdoc */ + destroy: function () { + if (this.dnd()) { + this.dnd().destroy(); + } + this._super(); + }, + /** * Calls 'initObservable' of parent * diff --git a/app/code/Magento/Version/Controller/Index/Index.php b/app/code/Magento/Version/Controller/Index/Index.php index 5c7c9b619facc..c5d02cd9a1fd2 100644 --- a/app/code/Magento/Version/Controller/Index/Index.php +++ b/app/code/Magento/Version/Controller/Index/Index.php @@ -9,7 +9,6 @@ use Magento\Framework\App\Action\Action; use Magento\Framework\App\Action\Context; use Magento\Framework\App\ProductMetadataInterface; -use Magento\Framework\Exception\StateException; /** * Magento Version controller diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_rules-temp.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_rules-temp.less index 29bbeb10f820d..a38cb17a95754 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_rules-temp.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_rules-temp.less @@ -31,6 +31,11 @@ padding-left: 0 !important; } + .x-tree li { + margin-bottom: 0 !important; + line-height: normal !important; + } + .legend { font-weight: @font-weight__semibold; } diff --git a/app/design/frontend/Magento/blank/web/css/print.less b/app/design/frontend/Magento/blank/web/css/print.less index 474bc27b0751b..ebfc6ce4300b7 100644 --- a/app/design/frontend/Magento/blank/web/css/print.less +++ b/app/design/frontend/Magento/blank/web/css/print.less @@ -132,4 +132,8 @@ .footer.content { padding: 0; } + + .hidden-print { + display: none !important; + } } diff --git a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less index fd004ef2c14d9..b5742b86cf9f1 100644 --- a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less @@ -528,6 +528,7 @@ .header.links { min-width: 175px; + z-index: 1000; } &.active { diff --git a/app/design/frontend/Magento/rush/LICENSE.txt b/app/design/frontend/Magento/rush/LICENSE.txt deleted file mode 100644 index 36b2459f6aa63..0000000000000 --- a/app/design/frontend/Magento/rush/LICENSE.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Open Software License ("OSL") v. 3.0 - -This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Open Software License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/design/frontend/Magento/rush/LICENSE_AFL.txt b/app/design/frontend/Magento/rush/LICENSE_AFL.txt deleted file mode 100644 index f39d641b18a19..0000000000000 --- a/app/design/frontend/Magento/rush/LICENSE_AFL.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Academic Free License ("AFL") v. 3.0 - -This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Academic Free License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/design/frontend/Magento/rush/Magento_Theme/layout/default.xml b/app/design/frontend/Magento/rush/Magento_Theme/layout/default.xml deleted file mode 100644 index 28e9d7e12f499..0000000000000 --- a/app/design/frontend/Magento/rush/Magento_Theme/layout/default.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/app/design/frontend/Magento/rush/Magento_Theme/layout/default_head_blocks.xml b/app/design/frontend/Magento/rush/Magento_Theme/layout/default_head_blocks.xml deleted file mode 100644 index 60fc5aa8f56ab..0000000000000 --- a/app/design/frontend/Magento/rush/Magento_Theme/layout/default_head_blocks.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/design/frontend/Magento/rush/Magento_Theme/layouts.xml b/app/design/frontend/Magento/rush/Magento_Theme/layouts.xml deleted file mode 100644 index 2ac2d369ade06..0000000000000 --- a/app/design/frontend/Magento/rush/Magento_Theme/layouts.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - App Shell - - - diff --git a/app/design/frontend/Magento/rush/Magento_Theme/page_layout/app_shell.xml b/app/design/frontend/Magento/rush/Magento_Theme/page_layout/app_shell.xml deleted file mode 100644 index a0fdf9bc9738e..0000000000000 --- a/app/design/frontend/Magento/rush/Magento_Theme/page_layout/app_shell.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/design/frontend/Magento/rush/Magento_Theme/templates/root.phtml b/app/design/frontend/Magento/rush/Magento_Theme/templates/root.phtml deleted file mode 100644 index 5a2fb56828cc4..0000000000000 --- a/app/design/frontend/Magento/rush/Magento_Theme/templates/root.phtml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - = /* @escapeNotVerified */ $headContent ?> - = /* @escapeNotVerified */ $headAdditional ?> - - - - - diff --git a/app/design/frontend/Magento/rush/composer.json b/app/design/frontend/Magento/rush/composer.json deleted file mode 100644 index 99bf02d92a990..0000000000000 --- a/app/design/frontend/Magento/rush/composer.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "magento/theme-frontend-rush", - "description": "N/A", - "require": { - "php": "~7.1.3||~7.2.0", - "magento/framework": "*" - }, - "type": "magento2-theme", - "license": ["OSL-3.0", "AFL-3.0"], - "autoload": { - "files": ["registration.php"] - } -} diff --git a/app/design/frontend/Magento/rush/etc/view.xml b/app/design/frontend/Magento/rush/etc/view.xml deleted file mode 100644 index 1b62c30fa9c43..0000000000000 --- a/app/design/frontend/Magento/rush/etc/view.xml +++ /dev/null @@ -1,311 +0,0 @@ - - - - - - - 140 - 140 - - - - 152 - 188 - - - 110 - 160 - - - 240 - 300 - - - 240 - 300 - - - 100 - 100 - - - 285 - 285 - - - 113 - 113 - - - 75 - 75 - - - 100 - 100 - - - 78 - 78 - - - 240 - 300 - - - 270 - 270 - - - 78 - 78 - - - 265 - 265 - - - 140 - 140 - - - - 700 - 700 - - - 90 - 90 - - - 700 - 700 - - - 700 - 700 - - - - 240 - 300 - - - 88 - 110 - - - 90 - 90 - - - 76 - 76 - - - 135 - 135 - - - 75 - 75 - - - 240 - 300 - - - 75 - 90 - - - 76 - 76 - - - 270 - 207 - - - 240 - 300 - - - 75 - 90 - - - 76 - 76 - - - 270 - 270 - - - 140 - 140 - - - 285 - 285 - - - 75 - 75 - - - 75 - 75 - - - 135 - 135 - - - 75 - 90 - - - 140 - 140 - - - 75 - 90 - - - 113 - 113 - - - 240 - 300 - - - - - - - - thumbs - true - true - true - false - true - horizontal - true - slides - - slide - 500 - - - thumbs - true - false - false - horizontal - slides - - dissolve - 500 - - - - - - 20 - - - - - hover - false - - - - - - 767px - - - - dots - - - - - - - 100 - 275 - 48 - - 166 - 370 - - 0 - - - 58 - - - 1MB - - - Lib::jquery/jquery.min.js - Lib::jquery/jquery-ui-1.9.2.js - Lib::jquery/jquery.details.js - Lib::jquery/jquery.details.min.js - Lib::jquery/jquery.hoverIntent.js - Lib::jquery/colorpicker/js/colorpicker.js - Lib::requirejs/require.js - Lib::requirejs/text.js - Lib::date-format-normalizer.js - Lib::legacy-build.min.js - Lib::mage/captcha.js - Lib::mage/dropdown_old.js - Lib::mage/list.js - Lib::mage/loader_old.js - Lib::mage/webapi.js - Lib::mage/zoom.js - Lib::mage/translate-inline-vde.js - Lib::mage/requirejs/mixins.js - Lib::mage/requirejs/static.js - Magento_Customer::js/zxcvbn.js - Magento_Catalog::js/zoom.js - Magento_Ui::js/lib/step-wizard.js - Magento_Ui::js/form/element/ui-select.js - Magento_Ui::js/form/element/file-uploader.js - Magento_Ui::js/form/components/insert.js - Magento_Ui::js/form/components/insert-listing.js - Magento_Ui::js/timeline - Magento_Ui::js/grid - Magento_Ui::js/dynamic-rows - Magento_Ui::templates/timeline - Magento_Ui::templates/grid - Magento_Ui::templates/dynamic-rows - Magento_Swagger::swagger-ui - Lib::modernizr - Lib::tiny_mce - Lib::varien - Lib::jquery/editableMultiselect - Lib::jquery/jstree - Lib::jquery/fileUploader - Lib::css - Lib::lib - Lib::extjs - Lib::prototype - Lib::scriptaculous - Lib::less - Lib::mage/adminhtml - Lib::mage/backend - - diff --git a/app/design/frontend/Magento/rush/media/preview.jpg b/app/design/frontend/Magento/rush/media/preview.jpg deleted file mode 100644 index 61be74876a1e9..0000000000000 Binary files a/app/design/frontend/Magento/rush/media/preview.jpg and /dev/null differ diff --git a/app/design/frontend/Magento/rush/registration.php b/app/design/frontend/Magento/rush/registration.php deleted file mode 100644 index d7b767d71e06d..0000000000000 --- a/app/design/frontend/Magento/rush/registration.php +++ /dev/null @@ -1,13 +0,0 @@ - - - - Magento Rush - - media/preview.jpg - - diff --git a/app/design/frontend/Magento/rush/web/css/_styles-l.less b/app/design/frontend/Magento/rush/web/css/_styles-l.less deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/app/design/frontend/Magento/rush/web/css/_styles-m.less b/app/design/frontend/Magento/rush/web/css/_styles-m.less deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/app/design/frontend/Magento/rush/web/images/logo.svg b/app/design/frontend/Magento/rush/web/images/logo.svg deleted file mode 100644 index 556d839b8b84f..0000000000000 --- a/app/design/frontend/Magento/rush/web/images/logo.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/design/frontend/Magento/rush/web/js/foo.js b/app/design/frontend/Magento/rush/web/js/foo.js deleted file mode 100644 index c4ea9770085d2..0000000000000 --- a/app/design/frontend/Magento/rush/web/js/foo.js +++ /dev/null @@ -1 +0,0 @@ -console.log('logging from rush/web'); diff --git a/app/etc/di.xml b/app/etc/di.xml index 7093b72af5c8e..6e3dabaa0751f 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -174,10 +174,10 @@ + + - - @@ -282,6 +282,13 @@ + + + + Magento\Framework\App\Feed + + + Cm\RedisSession\Handler\ConfigInterface @@ -1387,11 +1394,6 @@ - - - Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver - - diff --git a/composer.json b/composer.json index 856d078f8a340..e2a89ae1207a7 100644 --- a/composer.json +++ b/composer.json @@ -232,7 +232,6 @@ "magento/theme-adminhtml-backend": "*", "magento/theme-frontend-blank": "*", "magento/theme-frontend-luma": "*", - "magento/theme-frontend-rush": "*", "magento/language-de_de": "*", "magento/language-en_us": "*", "magento/language-es_es": "*", diff --git a/composer.lock b/composer.lock index ee7503ce57328..b104cf2929ba7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "b491d3c677cbffbbcb7a64da82bfbec5", - "content-hash": "7e60e718b5d717fa3cca1f7e588de9c4", + "content-hash": "daacd8800615d44aa1af0ac06c1ecc46", "packages": [ { "name": "braintree/braintree_php", @@ -52,7 +51,7 @@ } ], "description": "Braintree PHP Client Library", - "time": "2018-02-08 23:03:34" + "time": "2018-02-08T23:03:34+00:00" }, { "name": "colinmollenhour/cache-backend-file", @@ -85,7 +84,7 @@ ], "description": "The stock Zend_Cache_Backend_File backend has extremely poor performance for cleaning by tags making it become unusable as the number of cached items increases. This backend makes many changes resulting in a huge performance boost, especially for tag cleaning.", "homepage": "https://github.com/colinmollenhour/Cm_Cache_Backend_File", - "time": "2018-04-05 15:28:43" + "time": "2018-04-05T15:28:43+00:00" }, { "name": "colinmollenhour/cache-backend-redis", @@ -121,7 +120,7 @@ ], "description": "Zend_Cache backend using Redis with full support for tags.", "homepage": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis", - "time": "2017-10-05 20:50:44" + "time": "2017-10-05T20:50:44+00:00" }, { "name": "colinmollenhour/credis", @@ -161,7 +160,7 @@ ], "description": "Credis is a lightweight interface to the Redis key-value store which wraps the phpredis library when available for better performance.", "homepage": "https://github.com/colinmollenhour/credis", - "time": "2017-10-05 20:28:58" + "time": "2017-10-05T20:28:58+00:00" }, { "name": "colinmollenhour/php-redis-session-abstract", @@ -198,7 +197,7 @@ ], "description": "A Redis-based session handler with optimistic locking", "homepage": "https://github.com/colinmollenhour/php-redis-session-abstract", - "time": "2018-01-08 14:53:13" + "time": "2018-01-08T14:53:13+00:00" }, { "name": "composer/ca-bundle", @@ -254,7 +253,7 @@ "ssl", "tls" ], - "time": "2018-03-29 19:57:20" + "time": "2018-03-29T19:57:20+00:00" }, { "name": "composer/composer", @@ -331,7 +330,7 @@ "dependency", "package" ], - "time": "2018-04-13 10:04:24" + "time": "2018-04-13T10:04:24+00:00" }, { "name": "composer/semver", @@ -393,7 +392,7 @@ "validation", "versioning" ], - "time": "2016-08-30 16:08:34" + "time": "2016-08-30T16:08:34+00:00" }, { "name": "composer/spdx-licenses", @@ -454,7 +453,7 @@ "spdx", "validator" ], - "time": "2018-01-31 13:17:27" + "time": "2018-01-31T13:17:27+00:00" }, { "name": "container-interop/container-interop", @@ -485,7 +484,7 @@ ], "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", "homepage": "https://github.com/container-interop/container-interop", - "time": "2017-02-14 19:40:03" + "time": "2017-02-14T19:40:03+00:00" }, { "name": "elasticsearch/elasticsearch", @@ -540,7 +539,7 @@ "elasticsearch", "search" ], - "time": "2017-11-08 17:04:47" + "time": "2017-11-08T17:04:47+00:00" }, { "name": "guzzlehttp/ringphp", @@ -591,7 +590,7 @@ } ], "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", - "time": "2015-05-20 03:37:09" + "time": "2015-05-20T03:37:09+00:00" }, { "name": "guzzlehttp/streams", @@ -641,7 +640,7 @@ "Guzzle", "stream" ], - "time": "2014-10-12 19:18:40" + "time": "2014-10-12T19:18:40+00:00" }, { "name": "justinrainbow/json-schema", @@ -707,7 +706,7 @@ "json", "schema" ], - "time": "2018-02-14 22:26:30" + "time": "2018-02-14T22:26:30+00:00" }, { "name": "magento/composer", @@ -743,7 +742,7 @@ "AFL-3.0" ], "description": "Magento composer library helps to instantiate Composer application and run composer commands.", - "time": "2018-03-26 16:19:52" + "time": "2018-03-26T16:19:52+00:00" }, { "name": "magento/magento-composer-installer", @@ -822,7 +821,7 @@ "composer-installer", "magento" ], - "time": "2017-12-29 16:45:24" + "time": "2017-12-29T16:45:24+00:00" }, { "name": "magento/zendframework1", @@ -879,7 +878,7 @@ "source": "https://github.com/magento-engcom/zf1-php-7.2-support/tree/master", "issues": "https://github.com/magento-engcom/zf1-php-7.2-support/issues" }, - "time": "2018-04-06 17:12:22" + "time": "2018-04-06T17:12:22+00:00" }, { "name": "monolog/monolog", @@ -957,7 +956,7 @@ "logging", "psr-3" ], - "time": "2017-06-19 01:22:40" + "time": "2017-06-19T01:22:40+00:00" }, { "name": "oyejorge/less.php", @@ -1019,7 +1018,7 @@ "php", "stylesheet" ], - "time": "2017-03-28 22:19:25" + "time": "2017-03-28T22:19:25+00:00" }, { "name": "paragonie/random_compat", @@ -1067,7 +1066,7 @@ "pseudorandom", "random" ], - "time": "2018-04-04 21:24:14" + "time": "2018-04-04T21:24:14+00:00" }, { "name": "pelago/emogrifier", @@ -1136,7 +1135,7 @@ "email", "pre-processing" ], - "time": "2018-01-05 23:30:21" + "time": "2018-01-05T23:30:21+00:00" }, { "name": "php-amqplib/php-amqplib", @@ -1207,7 +1206,7 @@ "queue", "rabbitmq" ], - "time": "2018-02-11 19:28:00" + "time": "2018-02-11T19:28:00+00:00" }, { "name": "phpseclib/mcrypt_compat", @@ -1256,7 +1255,7 @@ "encryption", "mcrypt" ], - "time": "2018-01-13 23:07:52" + "time": "2018-01-13T23:07:52+00:00" }, { "name": "phpseclib/phpseclib", @@ -1348,7 +1347,7 @@ "x.509", "x509" ], - "time": "2018-04-15 16:55:05" + "time": "2018-04-15T16:55:05+00:00" }, { "name": "psr/container", @@ -1397,7 +1396,7 @@ "container-interop", "psr" ], - "time": "2017-02-14 16:28:37" + "time": "2017-02-14T16:28:37+00:00" }, { "name": "psr/http-message", @@ -1447,7 +1446,7 @@ "request", "response" ], - "time": "2016-08-06 14:39:51" + "time": "2016-08-06T14:39:51+00:00" }, { "name": "psr/log", @@ -1494,7 +1493,7 @@ "psr", "psr-3" ], - "time": "2016-10-10 12:19:37" + "time": "2016-10-10T12:19:37+00:00" }, { "name": "ramsey/uuid", @@ -1574,7 +1573,7 @@ "identifier", "uuid" ], - "time": "2018-01-20 00:28:24" + "time": "2018-01-20T00:28:24+00:00" }, { "name": "react/promise", @@ -1620,7 +1619,7 @@ "promise", "promises" ], - "time": "2017-03-25 12:08:31" + "time": "2017-03-25T12:08:31+00:00" }, { "name": "seld/cli-prompt", @@ -1668,7 +1667,7 @@ "input", "prompt" ], - "time": "2017-03-18 11:32:45" + "time": "2017-03-18T11:32:45+00:00" }, { "name": "seld/jsonlint", @@ -1717,7 +1716,7 @@ "parser", "validator" ], - "time": "2018-01-24 12:46:19" + "time": "2018-01-24T12:46:19+00:00" }, { "name": "seld/phar-utils", @@ -1761,7 +1760,7 @@ "keywords": [ "phra" ], - "time": "2015-10-13 18:44:15" + "time": "2015-10-13T18:44:15+00:00" }, { "name": "symfony/console", @@ -1829,7 +1828,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-04-03 05:24:00" + "time": "2018-04-03T05:24:00+00:00" }, { "name": "symfony/event-dispatcher", @@ -1892,7 +1891,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-04-06 07:35:43" + "time": "2018-04-06T07:35:43+00:00" }, { "name": "symfony/filesystem", @@ -1941,7 +1940,7 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-02-22 10:50:29" + "time": "2018-02-22T10:50:29+00:00" }, { "name": "symfony/finder", @@ -1990,7 +1989,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-04-04 05:10:37" + "time": "2018-04-04T05:10:37+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -2049,7 +2048,7 @@ "portable", "shim" ], - "time": "2018-01-30 19:27:44" + "time": "2018-01-30T19:27:44+00:00" }, { "name": "symfony/process", @@ -2098,7 +2097,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-04-03 05:24:00" + "time": "2018-04-03T05:24:00+00:00" }, { "name": "tedivm/jshrink", @@ -2144,7 +2143,7 @@ "javascript", "minifier" ], - "time": "2017-12-08 00:59:56" + "time": "2017-12-08T00:59:56+00:00" }, { "name": "tubalmartin/cssmin", @@ -2197,7 +2196,7 @@ "minify", "yui" ], - "time": "2018-01-15 15:26:51" + "time": "2018-01-15T15:26:51+00:00" }, { "name": "webonyx/graphql-php", @@ -2244,7 +2243,7 @@ "api", "graphql" ], - "time": "2017-12-12 09:03:21" + "time": "2017-12-12T09:03:21+00:00" }, { "name": "zendframework/zend-captcha", @@ -2301,7 +2300,7 @@ "captcha", "zf2" ], - "time": "2017-02-23 08:09:44" + "time": "2017-02-23T08:09:44+00:00" }, { "name": "zendframework/zend-code", @@ -2354,7 +2353,7 @@ "code", "zf2" ], - "time": "2017-10-20 15:21:32" + "time": "2017-10-20T15:21:32+00:00" }, { "name": "zendframework/zend-config", @@ -2410,7 +2409,7 @@ "config", "zf2" ], - "time": "2016-02-04 23:01:10" + "time": "2016-02-04T23:01:10+00:00" }, { "name": "zendframework/zend-console", @@ -2463,7 +2462,7 @@ "console", "zf" ], - "time": "2018-01-25 19:08:04" + "time": "2018-01-25T19:08:04+00:00" }, { "name": "zendframework/zend-crypt", @@ -2513,7 +2512,7 @@ "crypt", "zf2" ], - "time": "2016-02-03 23:46:30" + "time": "2016-02-03T23:46:30+00:00" }, { "name": "zendframework/zend-db", @@ -2571,7 +2570,7 @@ "db", "zf" ], - "time": "2018-04-09 13:21:36" + "time": "2018-04-09T13:21:36+00:00" }, { "name": "zendframework/zend-di", @@ -2618,7 +2617,7 @@ "di", "zf2" ], - "time": "2016-04-25 20:58:11" + "time": "2016-04-25T20:58:11+00:00" }, { "name": "zendframework/zend-diactoros", @@ -2670,7 +2669,7 @@ "psr", "psr-7" ], - "time": "2018-02-26 15:44:50" + "time": "2018-02-26T15:44:50+00:00" }, { "name": "zendframework/zend-escaper", @@ -2714,7 +2713,7 @@ "escaper", "zf2" ], - "time": "2016-06-30 19:48:38" + "time": "2016-06-30T19:48:38+00:00" }, { "name": "zendframework/zend-eventmanager", @@ -2761,7 +2760,7 @@ "eventmanager", "zf2" ], - "time": "2017-12-12 17:48:56" + "time": "2017-12-12T17:48:56+00:00" }, { "name": "zendframework/zend-feed", @@ -2822,7 +2821,7 @@ "feed", "zf" ], - "time": "2017-12-04 17:59:38" + "time": "2017-12-04T17:59:38+00:00" }, { "name": "zendframework/zend-filter", @@ -2885,7 +2884,7 @@ "filter", "zf" ], - "time": "2018-04-11 16:20:04" + "time": "2018-04-11T16:20:04+00:00" }, { "name": "zendframework/zend-form", @@ -2963,7 +2962,7 @@ "form", "zf" ], - "time": "2017-12-06 21:09:08" + "time": "2017-12-06T21:09:08+00:00" }, { "name": "zendframework/zend-http", @@ -3016,7 +3015,7 @@ "zend", "zf" ], - "time": "2017-10-13 12:06:24" + "time": "2017-10-13T12:06:24+00:00" }, { "name": "zendframework/zend-hydrator", @@ -3074,7 +3073,7 @@ "hydrator", "zf2" ], - "time": "2016-02-18 22:38:26" + "time": "2016-02-18T22:38:26+00:00" }, { "name": "zendframework/zend-i18n", @@ -3141,7 +3140,7 @@ "i18n", "zf2" ], - "time": "2017-05-17 17:00:12" + "time": "2017-05-17T17:00:12+00:00" }, { "name": "zendframework/zend-inputfilter", @@ -3194,7 +3193,7 @@ "inputfilter", "zf" ], - "time": "2018-01-22 19:41:18" + "time": "2018-01-22T19:41:18+00:00" }, { "name": "zendframework/zend-json", @@ -3249,7 +3248,7 @@ "json", "zf2" ], - "time": "2016-02-04 21:20:26" + "time": "2016-02-04T21:20:26+00:00" }, { "name": "zendframework/zend-loader", @@ -3293,7 +3292,7 @@ "loader", "zf2" ], - "time": "2015-06-03 14:05:47" + "time": "2015-06-03T14:05:47+00:00" }, { "name": "zendframework/zend-log", @@ -3364,7 +3363,7 @@ "logging", "zf2" ], - "time": "2018-04-09 21:59:51" + "time": "2018-04-09T21:59:51+00:00" }, { "name": "zendframework/zend-mail", @@ -3426,7 +3425,7 @@ "mail", "zf2" ], - "time": "2018-03-01 18:57:00" + "time": "2018-03-01T18:57:00+00:00" }, { "name": "zendframework/zend-math", @@ -3476,7 +3475,7 @@ "math", "zf2" ], - "time": "2016-04-07 16:29:53" + "time": "2016-04-07T16:29:53+00:00" }, { "name": "zendframework/zend-mime", @@ -3527,7 +3526,7 @@ "mime", "zf" ], - "time": "2017-11-28 15:02:22" + "time": "2017-11-28T15:02:22+00:00" }, { "name": "zendframework/zend-modulemanager", @@ -3587,7 +3586,7 @@ "modulemanager", "zf" ], - "time": "2017-12-02 06:11:18" + "time": "2017-12-02T06:11:18+00:00" }, { "name": "zendframework/zend-mvc", @@ -3679,7 +3678,7 @@ "mvc", "zf2" ], - "time": "2017-12-14 22:44:10" + "time": "2017-12-14T22:44:10+00:00" }, { "name": "zendframework/zend-psr7bridge", @@ -3728,7 +3727,7 @@ "psr", "psr-7" ], - "time": "2016-05-10 21:44:39" + "time": "2016-05-10T21:44:39+00:00" }, { "name": "zendframework/zend-serializer", @@ -3786,7 +3785,7 @@ "serializer", "zf2" ], - "time": "2017-11-20 22:21:04" + "time": "2017-11-20T22:21:04+00:00" }, { "name": "zendframework/zend-server", @@ -3832,7 +3831,7 @@ "server", "zf2" ], - "time": "2016-06-20 22:27:55" + "time": "2016-06-20T22:27:55+00:00" }, { "name": "zendframework/zend-servicemanager", @@ -3884,7 +3883,7 @@ "servicemanager", "zf2" ], - "time": "2017-12-05 16:27:36" + "time": "2017-12-05T16:27:36+00:00" }, { "name": "zendframework/zend-session", @@ -3951,7 +3950,7 @@ "session", "zf" ], - "time": "2018-02-22 16:33:54" + "time": "2018-02-22T16:33:54+00:00" }, { "name": "zendframework/zend-soap", @@ -4004,7 +4003,7 @@ "soap", "zf2" ], - "time": "2018-01-29 17:51:26" + "time": "2018-01-29T17:51:26+00:00" }, { "name": "zendframework/zend-stdlib", @@ -4063,7 +4062,7 @@ "stdlib", "zf2" ], - "time": "2016-04-12 21:17:31" + "time": "2016-04-12T21:17:31+00:00" }, { "name": "zendframework/zend-text", @@ -4110,7 +4109,7 @@ "text", "zf2" ], - "time": "2016-02-08 19:03:52" + "time": "2016-02-08T19:03:52+00:00" }, { "name": "zendframework/zend-uri", @@ -4157,7 +4156,7 @@ "uri", "zf2" ], - "time": "2018-04-10 17:08:10" + "time": "2018-04-10T17:08:10+00:00" }, { "name": "zendframework/zend-validator", @@ -4228,7 +4227,7 @@ "validator", "zf2" ], - "time": "2018-02-01 17:05:33" + "time": "2018-02-01T17:05:33+00:00" }, { "name": "zendframework/zend-view", @@ -4315,7 +4314,7 @@ "view", "zf2" ], - "time": "2018-01-17 22:21:50" + "time": "2018-01-17T22:21:50+00:00" } ], "packages-dev": [ @@ -4385,7 +4384,7 @@ "docblock", "parser" ], - "time": "2017-12-06 07:11:42" + "time": "2017-12-06T07:11:42+00:00" }, { "name": "doctrine/instantiator", @@ -4439,7 +4438,7 @@ "constructor", "instantiate" ], - "time": "2017-07-22 11:58:36" + "time": "2017-07-22T11:58:36+00:00" }, { "name": "doctrine/lexer", @@ -4493,7 +4492,7 @@ "lexer", "parser" ], - "time": "2014-09-09 13:34:57" + "time": "2014-09-09T13:34:57+00:00" }, { "name": "friendsofphp/php-cs-fixer", @@ -4580,7 +4579,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2018-03-20 18:07:08" + "time": "2018-03-20T18:07:08+00:00" }, { "name": "lusitanian/oauth", @@ -4647,7 +4646,7 @@ "oauth", "security" ], - "time": "2016-07-12 22:15:40" + "time": "2016-07-12T22:15:40+00:00" }, { "name": "myclabs/deep-copy", @@ -4692,7 +4691,7 @@ "object", "object graph" ], - "time": "2017-10-19 19:58:43" + "time": "2017-10-19T19:58:43+00:00" }, { "name": "pdepend/pdepend", @@ -4732,7 +4731,7 @@ "BSD-3-Clause" ], "description": "Official version of pdepend to be handled with Composer", - "time": "2017-12-13 13:21:38" + "time": "2017-12-13T13:21:38+00:00" }, { "name": "phar-io/manifest", @@ -4787,7 +4786,7 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05 18:14:27" + "time": "2017-03-05T18:14:27+00:00" }, { "name": "phar-io/version", @@ -4834,7 +4833,7 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05 17:38:23" + "time": "2017-03-05T17:38:23+00:00" }, { "name": "php-cs-fixer/diff", @@ -4885,7 +4884,7 @@ "keywords": [ "diff" ], - "time": "2018-02-15 16:58:55" + "time": "2018-02-15T16:58:55+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -4939,7 +4938,7 @@ "reflection", "static analysis" ], - "time": "2017-09-11 18:02:19" + "time": "2017-09-11T18:02:19+00:00" }, { "name": "phpdocumentor/reflection-docblock", @@ -4990,7 +4989,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30 07:14:17" + "time": "2017-11-30T07:14:17+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -5037,7 +5036,7 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14 14:27:02" + "time": "2017-07-14T14:27:02+00:00" }, { "name": "phpmd/phpmd", @@ -5103,7 +5102,7 @@ "phpmd", "pmd" ], - "time": "2017-01-20 14:41:10" + "time": "2017-01-20T14:41:10+00:00" }, { "name": "phpspec/prophecy", @@ -5166,7 +5165,7 @@ "spy", "stub" ], - "time": "2018-02-19 10:16:54" + "time": "2018-02-19T10:16:54+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5229,7 +5228,7 @@ "testing", "xunit" ], - "time": "2018-04-06 15:36:58" + "time": "2018-04-06T15:36:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5276,7 +5275,7 @@ "filesystem", "iterator" ], - "time": "2017-11-27 13:52:08" + "time": "2017-11-27T13:52:08+00:00" }, { "name": "phpunit/php-text-template", @@ -5317,7 +5316,7 @@ "keywords": [ "template" ], - "time": "2015-06-21 13:50:34" + "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", @@ -5366,7 +5365,7 @@ "keywords": [ "timer" ], - "time": "2017-02-26 11:10:40" + "time": "2017-02-26T11:10:40+00:00" }, { "name": "phpunit/php-token-stream", @@ -5415,7 +5414,7 @@ "keywords": [ "tokenizer" ], - "time": "2017-11-27 05:48:46" + "time": "2017-11-27T05:48:46+00:00" }, { "name": "phpunit/phpunit", @@ -5499,7 +5498,7 @@ "testing", "xunit" ], - "time": "2017-08-03 13:59:28" + "time": "2017-08-03T13:59:28+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -5558,7 +5557,7 @@ "mock", "xunit" ], - "time": "2017-08-03 14:08:16" + "time": "2017-08-03T14:08:16+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -5603,7 +5602,7 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04 06:30:41" + "time": "2017-03-04T06:30:41+00:00" }, { "name": "sebastian/comparator", @@ -5667,7 +5666,7 @@ "compare", "equality" ], - "time": "2017-03-03 06:26:08" + "time": "2017-03-03T06:26:08+00:00" }, { "name": "sebastian/diff", @@ -5719,7 +5718,7 @@ "keywords": [ "diff" ], - "time": "2017-05-22 07:24:03" + "time": "2017-05-22T07:24:03+00:00" }, { "name": "sebastian/environment", @@ -5769,7 +5768,7 @@ "environment", "hhvm" ], - "time": "2017-07-01 08:51:00" + "time": "2017-07-01T08:51:00+00:00" }, { "name": "sebastian/exporter", @@ -5836,7 +5835,7 @@ "export", "exporter" ], - "time": "2017-04-03 13:19:02" + "time": "2017-04-03T13:19:02+00:00" }, { "name": "sebastian/finder-facade", @@ -5875,7 +5874,7 @@ ], "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", "homepage": "https://github.com/sebastianbergmann/finder-facade", - "time": "2017-11-18 17:31:49" + "time": "2017-11-18T17:31:49+00:00" }, { "name": "sebastian/global-state", @@ -5926,7 +5925,7 @@ "keywords": [ "global state" ], - "time": "2017-04-27 15:39:26" + "time": "2017-04-27T15:39:26+00:00" }, { "name": "sebastian/object-enumerator", @@ -5973,7 +5972,7 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03 12:35:26" + "time": "2017-08-03T12:35:26+00:00" }, { "name": "sebastian/object-reflector", @@ -6018,7 +6017,7 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29 09:07:27" + "time": "2017-03-29T09:07:27+00:00" }, { "name": "sebastian/phpcpd", @@ -6068,7 +6067,7 @@ ], "description": "Copy/Paste Detector (CPD) for PHP code.", "homepage": "https://github.com/sebastianbergmann/phpcpd", - "time": "2017-11-16 08:49:28" + "time": "2017-11-16T08:49:28+00:00" }, { "name": "sebastian/recursion-context", @@ -6121,7 +6120,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03 06:23:57" + "time": "2017-03-03T06:23:57+00:00" }, { "name": "sebastian/resource-operations", @@ -6163,7 +6162,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28 20:34:47" + "time": "2015-07-28T20:34:47+00:00" }, { "name": "sebastian/version", @@ -6206,7 +6205,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03 07:35:21" + "time": "2016-10-03T07:35:21+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -6257,7 +6256,7 @@ "phpcs", "standards" ], - "time": "2017-12-19 21:44:46" + "time": "2017-12-19T21:44:46+00:00" }, { "name": "symfony/config", @@ -6319,7 +6318,7 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-03-19 22:35:49" + "time": "2018-03-19T22:35:49+00:00" }, { "name": "symfony/dependency-injection", @@ -6390,7 +6389,7 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-04-02 09:52:41" + "time": "2018-04-02T09:52:41+00:00" }, { "name": "symfony/options-resolver", @@ -6444,7 +6443,7 @@ "configuration", "options" ], - "time": "2018-01-18 22:19:33" + "time": "2018-01-18T22:19:33+00:00" }, { "name": "symfony/polyfill-php70", @@ -6503,7 +6502,7 @@ "portable", "shim" ], - "time": "2018-01-30 19:27:44" + "time": "2018-01-30T19:27:44+00:00" }, { "name": "symfony/polyfill-php72", @@ -6558,7 +6557,7 @@ "portable", "shim" ], - "time": "2018-01-31 17:43:24" + "time": "2018-01-31T17:43:24+00:00" }, { "name": "symfony/stopwatch", @@ -6607,7 +6606,7 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-02-19 16:50:22" + "time": "2018-02-19T16:50:22+00:00" }, { "name": "theseer/fdomdocument", @@ -6647,7 +6646,7 @@ ], "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", "homepage": "https://github.com/theseer/fDOMDocument", - "time": "2017-06-30 11:53:12" + "time": "2017-06-30T11:53:12+00:00" }, { "name": "theseer/tokenizer", @@ -6687,7 +6686,7 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07 12:08:54" + "time": "2017-04-07T12:08:54+00:00" }, { "name": "webmozart/assert", @@ -6737,7 +6736,7 @@ "check", "validate" ], - "time": "2018-01-29 19:49:41" + "time": "2018-01-29T19:49:41+00:00" } ], "aliases": [], diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backend/Page/AdminLogoutPage.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backend/Page/AdminLogoutPage.xml new file mode 100644 index 0000000000000..5660df619c73d --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backend/Page/AdminLogoutPage.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backend/Section/AdminMainActionsSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backend/Section/AdminMainActionsSection.xml new file mode 100644 index 0000000000000..3097a6962a064 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Backend/Section/AdminMainActionsSection.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/ProductData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/ProductData.xml index 3fe09e53dbdac..a8598ded6ec17 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/ProductData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Data/ProductData.xml @@ -17,5 +17,6 @@ checkbox 10 1 + bundleproduct diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Section/AdminProductFormBundleSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Section/AdminProductFormBundleSection.xml index 4598c9b984d20..551d0e44b58ae 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Section/AdminProductFormBundleSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Section/AdminProductFormBundleSection.xml @@ -9,12 +9,17 @@ + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Test/AdminAddRemoveProductImageBundleProductTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Test/AdminAddRemoveProductImageBundleProductTest.xml new file mode 100644 index 0000000000000..d39b633bedcba --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Bundle/Test/AdminAddRemoveProductImageBundleProductTest.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductActionGroup.xml index 436d7cf5a8d17..c4843260f9310 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductActionGroup.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductActionGroup.xml @@ -25,18 +25,41 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -64,6 +87,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductGridActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductGridActionGroup.xml index b153cf4a5b538..9bfbab2d5d872 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductGridActionGroup.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AdminProductGridActionGroup.xml @@ -29,6 +29,18 @@ + + + + + + + + + + + + @@ -82,6 +94,17 @@ + + + + + + + + + + + @@ -121,4 +144,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml index 6b9310cb95f53..3d4195d92f2c7 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml @@ -12,7 +12,7 @@ - + @@ -20,4 +20,26 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/StorefrontProductActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/StorefrontProductActionGroup.xml index ffc1687bc9f58..7673ce6874482 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/StorefrontProductActionGroup.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/ActionGroup/StorefrontProductActionGroup.xml @@ -23,4 +23,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/CategoryData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/CategoryData.xml index 54e9c476908f4..9ce257942b30d 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/CategoryData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/CategoryData.xml @@ -14,7 +14,7 @@ true - Api Category + ApiCategory true diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductAttributeData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductAttributeData.xml index 0adf1e91ae853..ab0eac4960dc6 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductAttributeData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductAttributeData.xml @@ -73,4 +73,25 @@ true ProductAttributeFrontendLabel + + testattribute + select + global + false + false + true + true + true + true + true + true + true + true + true + true + true + true + true + ProductAttributeFrontendLabel + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductData.xml index a9f270c4d4de0..cc6a32e7223f9 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Data/ProductData.xml @@ -38,6 +38,10 @@ ApiProductDescription ApiProductShortDescription + + Updated Api Simple Product + api-simple-product + SimpleProduct simple @@ -47,10 +51,34 @@ 4 1 1000 + simpleproduct 1 EavStockItem CustomAttributeCategoryIds + + SimpleProduct + simple + 4 + SimpleProduct + 123.00 + 4 + 1 + 1000 + EavStockItem + + + simple + simple + 4 + simple + 123.00 + 4 + 1 + 1000 + simple + EavStockItem + 321.00 @@ -120,6 +148,15 @@ Yes magento-logo.png + + MagentoLogo + 1.00 + Upload File + Yes + magento-logo.png + magento-logo + png + 霁产品 simple @@ -133,6 +170,20 @@ EavStockItem CustomAttributeCategoryIds + + virtualProduct + virtual + 4 + 4 + virtualProduct + 12.34 + virtualproduct + 1 + 100 + 0 + EavStockItem + CustomAttributeCategoryIds + testProductWithDescriptionSku simple @@ -148,6 +199,20 @@ ApiProductDescription ApiProductShortDescription + + api-simple-product + simple + 4 + 4 + Api Simple Product + 123.00 + api-simple-product + 1 + 100 + EavStockItem + ApiProductDescription + ApiProductShortDescription + testSku simple diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Page/AdminProductAttributesEditPage.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Page/AdminProductAttributesEditPage.xml new file mode 100644 index 0000000000000..e9a40a7541a98 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Page/AdminProductAttributesEditPage.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Page/AdminProductEditPage.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Page/AdminProductEditPage.xml new file mode 100644 index 0000000000000..a08c3e5fb1fec --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Page/AdminProductEditPage.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminEditProductAttributesSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminEditProductAttributesSection.xml new file mode 100644 index 0000000000000..bb0343c3a85c0 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminEditProductAttributesSection.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductFormSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductFormSection.xml index 08c3d42c02457..efc2de3c007e5 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductFormSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductFormSection.xml @@ -11,12 +11,12 @@ - + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductGridFilterSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductGridFilterSection.xml index ea822d223e4a2..3436bbf0c08d2 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductGridFilterSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductGridFilterSection.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> - + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductGridSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductGridSection.xml index c513c22ddde60..e4f571f7f0d83 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductGridSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductGridSection.xml @@ -23,6 +23,8 @@ + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductGridTableHeaderSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductGridTableHeaderSection.xml new file mode 100644 index 0000000000000..7b9fe410f7c94 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductGridTableHeaderSection.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductImagesSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductImagesSection.xml index bc59aae93296c..a9749ac677964 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductImagesSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminProductImagesSection.xml @@ -12,5 +12,7 @@ + + \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminUpdateAttributesSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminUpdateAttributesSection.xml new file mode 100644 index 0000000000000..7ef8d7706ba20 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/AdminUpdateAttributesSection.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontCategoryMainSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontCategoryMainSection.xml index d40fbbfc75d25..39275d61485a8 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontCategoryMainSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontCategoryMainSection.xml @@ -9,6 +9,7 @@ + @@ -18,5 +19,9 @@ + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontProductMediaSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontProductMediaSection.xml new file mode 100644 index 0000000000000..89b9a344e7d12 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Section/StorefrontProductMediaSection.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminAddRemoveProductImageSimpleProductTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminAddRemoveProductImageSimpleProductTest.xml new file mode 100644 index 0000000000000..f3ff2d1b3f635 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminAddRemoveProductImageSimpleProductTest.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminAddRemoveProductImageVirtualProductTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminAddRemoveProductImageVirtualProductTest.xml new file mode 100644 index 0000000000000..360b1b4d95819 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminAddRemoveProductImageVirtualProductTest.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminCreateProductDuplicateUrlkeyTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminCreateProductDuplicateUrlkeyTest.xml index 9f16ce22745ee..bb5583f0c3d1a 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminCreateProductDuplicateUrlkeyTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminCreateProductDuplicateUrlkeyTest.xml @@ -15,8 +15,7 @@ - - + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminCreateSimpleProductWithUnicodeTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminCreateSimpleProductWithUnicodeTest.xml index 46cf809ce9d47..ace6cf7198fc4 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminCreateSimpleProductWithUnicodeTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminCreateSimpleProductWithUnicodeTest.xml @@ -15,8 +15,7 @@ - - + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml new file mode 100644 index 0000000000000..f0d50c8a5f7d6 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml new file mode 100644 index 0000000000000..96b8032f88751 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml new file mode 100644 index 0000000000000..00a2e41e2dfb3 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/StorefrontProductWithEmptyAttributeTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/StorefrontProductWithEmptyAttributeTest.xml new file mode 100644 index 0000000000000..f858340bc09fa --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Catalog/Test/StorefrontProductWithEmptyAttributeTest.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/ActionGroup/StorefrontCatalogSearchActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/ActionGroup/StorefrontCatalogSearchActionGroup.xml index 88daae5679085..5e6bfeb448e13 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/ActionGroup/StorefrontCatalogSearchActionGroup.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/ActionGroup/StorefrontCatalogSearchActionGroup.xml @@ -42,6 +42,38 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Section/StorefrontCatalogSearchAdvancedResultMainSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Section/StorefrontCatalogSearchAdvancedResultMainSection.xml index 25671dc811285..f211561177da4 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Section/StorefrontCatalogSearchAdvancedResultMainSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/Section/StorefrontCatalogSearchAdvancedResultMainSection.xml @@ -15,5 +15,6 @@ + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/composer.json b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/composer.json index a31c7e6dc526c..8446dd8fba446 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/composer.json +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/CatalogSearch/composer.json @@ -16,6 +16,7 @@ "magento/magento2-functional-test-module-search": "100.0.0-dev", "magento/magento2-functional-test-module-theme": "100.0.0-dev", "magento/magento2-functional-test-module-ui": "100.0.0-dev", + "magento/magento2-functional-test-module-store": "100.0.0-dev", "php": "~7.1.3||~7.2.0" }, "suggest": { @@ -23,8 +24,7 @@ "magento/magento2-functional-test-module-catalog-inventory": "100.0.0-dev", "magento/magento2-functional-test-module-customer": "100.0.0-dev", "magento/magento2-functional-test-module-directory": "100.0.0-dev", - "magento/magento2-functional-test-module-eav": "100.0.0-dev", - "magento/magento2-functional-test-module-store": "100.0.0-dev" + "magento/magento2-functional-test-module-eav": "100.0.0-dev" }, "autoload": { "psr-4": { diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/ActionGroup/AdminConfigurableProductActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/ActionGroup/AdminConfigurableProductActionGroup.xml index b1845bdff03b1..c2866816110c5 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/ActionGroup/AdminConfigurableProductActionGroup.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/ActionGroup/AdminConfigurableProductActionGroup.xml @@ -35,4 +35,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Section/AdminProductFormConfigurationsSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Section/AdminProductFormConfigurationsSection.xml index 4cb5c680090a4..40d700fd4b75c 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Section/AdminProductFormConfigurationsSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Section/AdminProductFormConfigurationsSection.xml @@ -16,6 +16,9 @@ + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminAddRemoveProductImageConfigurableTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminAddRemoveProductImageConfigurableTest.xml new file mode 100644 index 0000000000000..52dc1bf0b5390 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminAddRemoveProductImageConfigurableTest.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductCreateTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductCreateTest.xml new file mode 100644 index 0000000000000..55a7e21729d78 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductCreateTest.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductDeleteTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductDeleteTest.xml new file mode 100644 index 0000000000000..02f742cd5f1c4 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductDeleteTest.xml @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductSearchTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductSearchTest.xml new file mode 100644 index 0000000000000..5da689aa5e61c --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductSearchTest.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductUpdateTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductUpdateTest.xml new file mode 100644 index 0000000000000..646bf9f65bbe5 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminConfigurableProductUpdateTest.xml @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminCreateTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminCreateTest.xml deleted file mode 100644 index 133c39f7bb444..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminCreateTest.xml +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminRelatedProductsTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminRelatedProductsTest.xml index 74247c1a98c15..0d773cb6df666 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminRelatedProductsTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/AdminRelatedProductsTest.xml @@ -12,11 +12,10 @@ - - + + - - + @@ -82,7 +81,7 @@ - + @@ -99,7 +98,7 @@ - + @@ -144,7 +143,7 @@ - + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/StorefrontConfigurableProductViewTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/StorefrontConfigurableProductViewTest.xml new file mode 100644 index 0000000000000..447bb6683bca0 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProduct/Test/StorefrontConfigurableProductViewTest.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{_defaultProduct.urlKey}} + {$grabUrl} + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/ActionGroup/AdminDownloadableProductActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/ActionGroup/AdminDownloadableProductActionGroup.xml index f760156facb65..c1370ddcfb95e 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/ActionGroup/AdminDownloadableProductActionGroup.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/ActionGroup/AdminDownloadableProductActionGroup.xml @@ -25,7 +25,7 @@ - + @@ -42,7 +42,7 @@ - + @@ -59,7 +59,7 @@ - + @@ -72,7 +72,7 @@ - + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/ProductData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/ProductData.xml index f7132a172704f..427f2577a8ab9 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/ProductData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Data/ProductData.xml @@ -17,5 +17,6 @@ 100 0 1 + downloadableproduct diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Test/AdminAddRemoveProductImageDownloadableProductTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Test/AdminAddRemoveProductImageDownloadableProductTest.xml new file mode 100644 index 0000000000000..6b086a3d4050b --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Downloadable/Test/AdminAddRemoveProductImageDownloadableProductTest.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/GroupedProductData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/GroupedProductData.xml index 7c2ddd35dabed..019b0d24dec52 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/GroupedProductData.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Data/GroupedProductData.xml @@ -14,6 +14,7 @@ 4 GroupedProduct 1 + groupedproduct EavStockItem diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Section/AdminAddProductsToGroupPanelSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Section/AdminAddProductsToGroupPanelSection.xml index f8b0490983178..45e32e7f2cd04 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Section/AdminAddProductsToGroupPanelSection.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Section/AdminAddProductsToGroupPanelSection.xml @@ -14,5 +14,6 @@ + \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Test/AdminAddRemoveProductImageGroupedProductTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Test/AdminAddRemoveProductImageGroupedProductTest.xml new file mode 100644 index 0000000000000..84ecec485db6d --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GroupedProduct/Test/AdminAddRemoveProductImageGroupedProductTest.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/ActionGroup/AdminProductVideoActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/ActionGroup/AdminProductVideoActionGroup.xml new file mode 100644 index 0000000000000..9fe81d56efa84 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/ActionGroup/AdminProductVideoActionGroup.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/ActionGroup/StorefrontProductVideoActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/ActionGroup/StorefrontProductVideoActionGroup.xml new file mode 100644 index 0000000000000..3aba592dee0b0 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/ActionGroup/StorefrontProductVideoActionGroup.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Data/ProductVideoConfigData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Data/ProductVideoConfigData.xml new file mode 100644 index 0000000000000..2873febbe1e97 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Data/ProductVideoConfigData.xml @@ -0,0 +1,26 @@ + + + + + + + YouTubeApiKey + + + AIzaSyDwqDWuw1lra-LnpJL2Mr02DYuFmkuRSns + + + + + DefaultYouTubeApiKey + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Data/ProductVideoData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Data/ProductVideoData.xml new file mode 100644 index 0000000000000..05ecb1a69444a --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Data/ProductVideoData.xml @@ -0,0 +1,16 @@ + + + + + + https://youtu.be/bpOSxM0rNPM + Arctic Monkeys - Do I Wanna Know? (Official Video) + Arctic Monkeys + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Metadata/product_video_config-meta.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Metadata/product_video_config-meta.xml new file mode 100644 index 0000000000000..be54d9038d0d1 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Metadata/product_video_config-meta.xml @@ -0,0 +1,21 @@ + + + + + + + + + string + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Section/AdminProductImagesSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Section/AdminProductImagesSection.xml new file mode 100644 index 0000000000000..66a93a6ec4b36 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Section/AdminProductImagesSection.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Section/AdminProductNewVideoSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Section/AdminProductNewVideoSection.xml new file mode 100644 index 0000000000000..8f55468a12748 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Section/AdminProductNewVideoSection.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Section/StorefrontProductInfoMainSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Section/StorefrontProductInfoMainSection.xml new file mode 100644 index 0000000000000..33d9cc8d0a03f --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/Section/StorefrontProductInfoMainSection.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/composer.json b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/composer.json index 0e1f83433049a..40d03272a3292 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/composer.json +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ProductVideo/composer.json @@ -12,11 +12,11 @@ }, "require": { "magento/magento2-functional-testing-framework": "1.0.0", + "magento/magento2-functional-test-module-catalog": "100.0.0-dev", "php": "~7.1.3||~7.2.0" }, "suggest": { "magento/magento2-functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-test-module-catalog": "100.0.0-dev", "magento/magento2-functional-test-module-eav": "100.0.0-dev", "magento/magento2-functional-test-module-media-storage": "100.0.0-dev", "magento/magento2-functional-test-module-store": "100.0.0-dev" diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Page/PriceRuleNewPage.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Page/PriceRuleNewPage.xml new file mode 100644 index 0000000000000..c8475de4c61f7 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Page/PriceRuleNewPage.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Section/PriceRuleConditionsSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Section/PriceRuleConditionsSection.xml new file mode 100644 index 0000000000000..39c6dd6b31968 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Section/PriceRuleConditionsSection.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/PriceRuleCategoryNestingTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/PriceRuleCategoryNestingTest.xml new file mode 100644 index 0000000000000..9d47a5e823e83 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/PriceRuleCategoryNestingTest.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Store/ActionGroup/AdminSwitchStoreViewActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Store/ActionGroup/AdminSwitchStoreViewActionGroup.xml new file mode 100644 index 0000000000000..8c4ecbed047b8 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Store/ActionGroup/AdminSwitchStoreViewActionGroup.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Store/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Store/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml new file mode 100644 index 0000000000000..9f06c2773dc3a --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Store/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Store/Section/AdminMainActionsSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Store/Section/AdminMainActionsSection.xml new file mode 100644 index 0000000000000..14429c298b5e5 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Store/Section/AdminMainActionsSection.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Theme/Page/ThemesPage.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Theme/Page/ThemesPage.xml new file mode 100644 index 0000000000000..358d88875d02f --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Theme/Page/ThemesPage.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Theme/Section/AdminThemeSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Theme/Section/AdminThemeSection.xml new file mode 100644 index 0000000000000..281cc290b4f39 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Theme/Section/AdminThemeSection.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Theme/Test/ThemeTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Theme/Test/ThemeTest.xml new file mode 100644 index 0000000000000..8764750d473c3 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Theme/Test/ThemeTest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php index 582508184b5d6..5fb2020de3e81 100644 --- a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php @@ -31,8 +31,8 @@ public function testGet() 'position' => 0, 'can_change_quantity' => 1, 'is_default' => false, - 'price' => null, - 'price_type' => null, + 'price' => 2.75, + 'price_type' => 0, ], ], ]; @@ -72,8 +72,8 @@ public function testGetList() 'position' => 0, 'can_change_quantity' => 1, 'is_default' => false, - 'price' => null, - 'price_type' => null, + 'price' => 2.75, + 'price_type' => 0, ], ], ], diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php index 7d5143faf937a..8ccd9d0c94f61 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php @@ -40,7 +40,6 @@ public function testAllFieldsBundleProducts() ... on PhysicalProductInterface { weight } - category_ids ... on BundleProduct { dynamic_sku dynamic_price @@ -126,7 +125,6 @@ public function testBundleProdutWithNotVisibleChildren() ... on PhysicalProductInterface { weight } - category_ids ... on BundleProduct { dynamic_sku dynamic_price @@ -329,7 +327,6 @@ public function testAndMaxMinPriceBundleProduct() ... on PhysicalProductInterface { weight } - category_ids price { minimalPrice { amount { @@ -434,7 +431,6 @@ public function testNonExistentFieldQtyExceptionOnBundleProduct() ... on PhysicalProductInterface { weight } - category_ids ... on BundleProduct { dynamic_sku diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index e193b556fa942..eb138d738ea10 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -32,13 +32,10 @@ public function testCategoriesTree() $rootCategoryId = 2; $query = <<getData('categories/category_tree/children/7/children/1/description') + $responseDataObject->getData('category/children/7/children/1/description') ); self::assertEquals( 'default-category', - $responseDataObject->getData('categories/category_tree/url_key') + $responseDataObject->getData('category/url_key') ); self::assertEquals( [], - $responseDataObject->getData('categories/category_tree/children/0/available_sort_by') + $responseDataObject->getData('category/children/0/available_sort_by') ); self::assertEquals( 'name', - $responseDataObject->getData('categories/category_tree/children/0/default_sort_by') + $responseDataObject->getData('category/children/0/default_sort_by') ); self::assertCount( 8, - $responseDataObject->getData('categories/category_tree/children') + $responseDataObject->getData('category/children') ); self::assertCount( 2, - $responseDataObject->getData('categories/category_tree/children/7/children') + $responseDataObject->getData('category/children/7/children') ); self::assertEquals( 5, - $responseDataObject->getData('categories/category_tree/children/7/children/1/children/0/id') + $responseDataObject->getData('category/children/7/children/1/children/0/id') ); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index b6866c1374069..b95e0f933ea04 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -616,7 +616,9 @@ public function testFilteringForProductInMultipleCategories() sku name attribute_set_id - category_ids + categories { + id + } } } } @@ -630,11 +632,11 @@ public function testFilteringForProductInMultipleCategories() $product = $productRepository->get('simple333'); $categoryIds = $product->getCategoryIds(); foreach ($categoryIds as $index => $value) { - $categoryIds[$index] = (int)$value; + $categoryIds[$index] = [ 'id' => (int)$value]; } - $this->assertNotEmpty($response['products']['items'][0]['category_ids'], "Category_ids must not be empty"); - $this->assertNotNull($response['products']['items'][0]['category_ids'], "categoy_ids must not be null"); - $this->assertEquals($categoryIds, $response['products']['items'][0]['category_ids']); + $this->assertNotEmpty($response['products']['items'][0]['categories'], "Categories must not be empty"); + $this->assertNotNull($response['products']['items'][0]['categories'], "categories must not be null"); + $this->assertEquals($categoryIds, $response['products']['items'][0]['categories']); /** @var MetadataPool $metaData */ $metaData = ObjectManager::getInstance()->get(MetadataPool::class); $linkField = $metaData->getMetadata(ProductInterface::class)->getLinkField(); @@ -672,14 +674,12 @@ public function testFilterProductsByCategoryIds() sku name type_id - category_ids categories{ name id path children_count product_count - is_active } } total_count @@ -710,7 +710,6 @@ public function testFilterProductsByCategoryIds() foreach ($categoryIds as $index => $value) { $categoryIds[$index] = (int)$value; } - $this->assertEquals($response['products']['items'][$itemIndex]['category_ids'], $categoryIds); $categoryInResponse = array_map( null, $categoryIds, @@ -728,7 +727,6 @@ public function testFilterProductsByCategoryIds() 'path' => $category->getPath(), 'children_count' => $category->getChildrenCount(), 'product_count' => $category->getProductCount(), - 'is_active' => $category->getIsActive(), ] ); } @@ -1144,7 +1142,6 @@ public function testQueryWithNoSearchOrFilterArgumentException() ... on PhysicalProductInterface { weight } - category_ids } } } @@ -1178,7 +1175,6 @@ public function testFilterProductsThatAreOutOfStockWithConfigSettings() { sku name - category_ids } total_count diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php index 34c5402a3e8d0..a4c7f41df0e99 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php @@ -44,14 +44,11 @@ public function testQueryAllFieldsSimpleProduct() attribute_set_id country_of_manufacture created_at - custom_layout - custom_layout_update description gift_message_available id categories { name - is_active url_path available_sort_by level @@ -292,15 +289,12 @@ public function testQueryMediaGalleryEntryFieldsSimpleProduct() { items{ attribute_set_id - category_ids categories { id } country_of_manufacture created_at - custom_layout - custom_layout_update description gift_message_available id diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php index 6ce4b522047ab..84c4e80d325ab 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php @@ -89,7 +89,6 @@ public function testQueryConfigurableProductLinks() } } } - category_ids ... on ConfigurableProduct { configurable_options { id @@ -110,7 +109,6 @@ public function testQueryConfigurableProductLinks() variants { product { id - category_ids name sku attribute_set_id @@ -305,10 +303,6 @@ private function assertConfigurableVariants($actualResponse) ); $indexValue = $variantArray['product']['sku']; unset($variantArray['product']['id']); - $this->assertTrue( - isset($variantArray['product']['category_ids']), - 'variant product doesn\'t contain category_ids key' - ); $this->assertTrue( isset($variantArray['product']['categories']), 'variant product doesn\'t contain categories key' @@ -327,12 +321,6 @@ private function assertConfigurableVariants($actualResponse) $this->assertEquals($actualValue, ['id' => $id]); unset($variantArray['product']['categories']); - $categoryIdsAttribute = $childProduct->getCustomAttribute('category_ids'); - $this->assertNotEmpty($categoryIdsAttribute, "Precondition failed: 'category_ids' must not be empty"); - $categoryIdsAttributeValue = $categoryIdsAttribute ? $categoryIdsAttribute->getValue() : []; - $this->assertEquals($categoryIdsAttributeValue, $variantArray['product']['category_ids']); - unset($variantArray['product']['category_ids']); - $mediaGalleryEntries = $childProduct->getMediaGalleryEntries(); $this->assertCount( 1, diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php index d8929bab8e9e5..57d39f04347bf 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php @@ -48,7 +48,6 @@ public function testQueryAllFieldsDownloadableProductsWithDownloadableFileAndSam } } } - category_ids ... on DownloadableProduct { links_title links_purchased_separately @@ -139,7 +138,6 @@ public function testDownloadableProductQueryWithNoSample() } } } - category_ids ... on DownloadableProduct { links_title links_purchased_separately diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php index 453a7b5940e1d..3aa6fc8f2a84f 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php @@ -192,6 +192,14 @@ public function getDependencyCheck() return $this->_rootElement->find($this->dependencyCheck, Locator::SELECTOR_CSS)->getText(); } + /** + * @return bool + */ + public function isPhpVersionCheckVisible() : bool + { + return $this->_rootElement->find($this->phpVersionCheck)->isVisible(); + } + /** * Get PHP Version check result. * diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php index ea6355d574549..5cb71d85a51ce 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php @@ -10,6 +10,7 @@ use Magento\Mtf\Client\Element\SimpleElement; use Magento\Mtf\Client\Locator; use Magento\Mtf\Fixture\FixtureInterface; +use Magento\Setup\Test\Block\SelectVersion\OtherComponentsGrid; /** * Select version block. @@ -37,6 +38,29 @@ class SelectVersion extends Form */ private $showAllVersions = '#showUnstable'; + /** + * CSS selector for Other Components Grid Block. + * + * @var string + */ + private $otherComponentsGrid = '.admin__data-grid-outer-wrap'; + + /** + * @var string + */ + private $empty = '[ng-show="componentsProcessed && total == 0"]'; + + /** + * @var string + */ + private $waitEmpty = + '//div[contains(@ng-show, "componentsProcessed && total") and not(contains(@class,"ng-hide"))]'; + + /** + * @var OtherComponentsGrid + */ + private $otherComponentGrid; + /** * Click on 'Next' button. * @@ -76,13 +100,59 @@ private function chooseShowAllVersions() } /** - * Choose 'yes' for upgrade option called 'Other components' + * Choose 'yes' for upgrade option called 'Other components'. * + * @param array $packages * @return void */ - public function chooseUpgradeOtherComponents() + public function chooseUpgradeOtherComponents(array $packages) :void { - $this->_rootElement->find("[for=yesUpdateComponents]", Locator::SELECTOR_CSS)->click(); - $this->waitForElementVisible("[ng-show='componentsProcessed']"); + $this->_rootElement->find("[for=yesUpdateComponents]")->click(); + $this->waitForElementNotVisible("[ng-show=\"!componentsProcessed\""); + + if (!$this->isComponentsEmpty()) { + $otherComponentGrid = $this->getOtherComponentsGrid(); + $otherComponentGrid->setItemsPerPage(200); + $otherComponentGrid->setVersions($packages); + } + } + + /** + * Check that grid is empty. + * + * @return bool + */ + public function isComponentsEmpty() + { + $this->waitForElementVisible($this->waitEmpty, Locator::SELECTOR_XPATH); + + return $this->_rootElement->find($this->empty)->isVisible(); + } + + /** + * Returns selected packages. + * + * @return array + */ + public function getSelectedPackages() + { + return $this->getOtherComponentsGrid()->getSelectedPackages(); + } + + /** + * Get grid block for other components. + * + * @return OtherComponentsGrid + */ + private function getOtherComponentsGrid() : OtherComponentsGrid + { + if (!isset($this->otherComponentGrid)) { + $this->otherComponentGrid = $this->blockFactory->create( + OtherComponentsGrid::class, + ['element' => $this->_rootElement->find($this->otherComponentsGrid)] + ); + } + + return $this->otherComponentGrid; } } diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php new file mode 100644 index 0000000000000..8ca2c0654e28c --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php @@ -0,0 +1,88 @@ +itemComponent, $package['name']); + $elements = $this->_rootElement->getElements($selector, Locator::SELECTOR_XPATH); + foreach ($elements as $element) { + $row = $this->getComponentRow($element); + $row->setVersion($package['version']); + $this->selectedPackages[$row->getPackageName()] = $package['version']; + } + } + } + + /** + * Returns selected packages. + * + * @return array + */ + public function getSelectedPackages() : array + { + return $this->selectedPackages; + } + + /** + * Set pager size. + * + * @param int $count + * @return void + */ + public function setItemsPerPage(int $count) : void + { + $this->_rootElement->find($this->perPage, Locator::SELECTOR_CSS, 'select')->setValue($count); + } + + /** + * Get component block. + * + * @param ElementInterface $element + * @return Item + */ + private function getComponentRow(ElementInterface $element) : Item + { + return $this->blockFactory->create( + Item::class, + ['element' => $element] + ); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php new file mode 100644 index 0000000000000..8c24323f37618 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php @@ -0,0 +1,51 @@ +_rootElement->find($this->version, Locator::SELECTOR_CSS, 'select')->setValue($version); + } + + /** + * Returns package name of element. + * + * @return array|string + */ + public function getPackageName() + { + return $this->_rootElement->find($this->packageName)->getText(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php index 5ddc5794ff816..caafa814a871a 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php @@ -18,17 +18,24 @@ class AssertVersionAndEditionCheck extends AbstractConstraint * Assert that package and version is correct * * @param SetupWizard $setupWizard - * @param string $package - * @param string $version + * @param array $upgrade * @return void */ - public function processAssert(SetupWizard $setupWizard, $package, $version) + public function processAssert(SetupWizard $setupWizard, array $upgrade) :void { - $message = "We're ready to upgrade $package to $version"; + $message = "We're ready to upgrade {$upgrade['package']} to {$upgrade['version']}."; + if ($upgrade['otherComponents'] === 'Yes' && isset($upgrade['selectedPackages'])) { + foreach ($upgrade['selectedPackages'] as $name => $version) { + $message .= "\nWe're ready to upgrade {$name} to {$version}."; + } + } + $actualMessage = $setupWizard->getSystemUpgrade()->getUpgradeMessage(); \PHPUnit\Framework\Assert::assertContains( $message, - $setupWizard->getSystemUpgrade()->getUpgradeMessage(), - 'Updater application check is incorrect.' + $actualMessage, + "Updater application check is incorrect: \n" + . "Expected: '$message' \n" + . "Actual: '$actualMessage'" ); } diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php index 53c36e0a1e1b0..c9b3ce35e84ed 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php @@ -31,23 +31,18 @@ class UpgradeSystemTest extends Injectable protected $adminDashboard; /** - * @var \Magento\Mtf\Util\Iterator\ApplicationState - */ - private $applicationStateIterator; - - /** + * Injection data. + * * @param Dashboard $adminDashboard * @param SetupWizard $setupWizard - * @param \Magento\Mtf\Util\Iterator\ApplicationState $applicationStateIterator + * @return void */ public function __inject( Dashboard $adminDashboard, - SetupWizard $setupWizard, - \Magento\Mtf\Util\Iterator\ApplicationState $applicationStateIterator + SetupWizard $setupWizard ) { $this->adminDashboard = $adminDashboard; $this->setupWizard = $setupWizard; - $this->applicationStateIterator = $applicationStateIterator; } /** @@ -114,7 +109,7 @@ public function test( $this->setupWizard->getSetupHome()->clickSystemUpgrade(); $this->setupWizard->getSelectVersion()->fill($upgradeFixture); if ($upgrade['otherComponents'] === 'Yes') { - $this->setupWizard->getSelectVersion()->chooseUpgradeOtherComponents(); + $this->setupWizard->getSelectVersion()->chooseUpgradeOtherComponents($upgrade['otherComponentsList']); } $this->setupWizard->getSelectVersion()->clickNext(); @@ -128,7 +123,9 @@ public function test( $this->setupWizard->getCreateBackup()->clickNext(); // Check info and press 'Upgrade' button - $assertVersionAndEdition->processAssert($this->setupWizard, $upgrade['package'], $version); + $upgrade['version'] = $version; + $upgrade['selectedPackages'] = $this->setupWizard->getSelectVersion()->getSelectedPackages(); + $assertVersionAndEdition->processAssert($this->setupWizard, $upgrade); $this->setupWizard->getSystemUpgrade()->clickSystemUpgrade(); $assertSuccessMessage->processAssert($this->setupWizard, $upgrade['package']); diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.xml b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.xml index b092fe1812201..95193dbf6c097 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.xml +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.xml @@ -18,6 +18,13 @@ No No {otherComponents} + + + + {package_0_name} + {package_0_version} + + diff --git a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php index 6f6f1b9d818af..dc525a46428c4 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php @@ -9,6 +9,8 @@ */ namespace Magento\TestFramework\Annotation; +use PHPUnit\Framework\Exception; + class DataFixture { /** @@ -171,8 +173,13 @@ protected function _applyOneFixture($fixture) require $fixture; } } catch (\Exception $e) { - throw new \Exception( - sprintf("Error in fixture: %s.\n %s", json_encode($fixture), $e->getMessage()), + throw new Exception( + sprintf( + "Error in fixture: %s.\n %s\n %s", + json_encode($fixture), + $e->getMessage(), + $e->getTraceAsString() + ), 500, $e ); diff --git a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php index 423bd22f0a8a9..db7f57362d807 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php @@ -9,6 +9,8 @@ */ namespace Magento\TestFramework\Annotation; +use PHPUnit\Framework\Exception; + class DataFixtureBeforeTransaction { /** @@ -138,8 +140,15 @@ protected function _applyOneFixture($fixture) require $fixture; } } catch (\Exception $e) { - throw new \Exception( - sprintf("Error in fixture: %s.\n %s", json_encode($fixture), (string)$e) + throw new Exception( + sprintf( + "Error in fixture: %s.\n %s\n %s", + json_encode($fixture), + $e->getMessage(), + $e->getTraceAsString() + ), + 500, + $e ); } } diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/OptionRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/OptionRepositoryTest.php new file mode 100644 index 0000000000000..d9f1095000c0a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/OptionRepositoryTest.php @@ -0,0 +1,78 @@ +objectManager = Bootstrap::getObjectManager(); + + $this->optionRepository = $this->objectManager->get(OptionRepository::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/products.php + * @magentoDataFixture Magento/Bundle/_files/empty_bundle_product.php + */ + public function testBundleProductIsSaleableAfterNewOptionSave() + { + $bundleProduct = $this->productRepository->get('bundle-product'); + + /** @var OptionInterface $newOption */ + $newOption = $this->objectManager->create(OptionInterfaceFactory::class)->create(); + /** @var LinkInterface $productLink */ + $productLink = $this->objectManager->create(LinkInterfaceFactory::class)->create(); + + $newOption->setTitle('new-option'); + $newOption->setRequired(true); + $newOption->setType('select'); + $newOption->setSku($bundleProduct->getSku()); + + $productLink->setSku('simple'); + $productLink->setQty(1); + $productLink->setIsDefault(true); + $productLink->setCanChangeQuantity(0); + + $newOption->setProductLinks([$productLink]); + + $optionId = $this->optionRepository->save($bundleProduct, $newOption); + $bundleProduct = $this->productRepository->get($bundleProduct->getSku(), false, null, true); + + $this->assertNotNull($optionId, 'Bundle option was not saved correctly'); + $this->assertTrue($bundleProduct->isSaleable(), 'Bundle product should show as in stock once option is added'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product.php new file mode 100644 index 0000000000000..cc20b293e69d1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product.php @@ -0,0 +1,29 @@ +create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId('bundle') + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Bundle Product') + ->setSku('bundle-product') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->setPriceView(0) + ->setPriceType(0) + ->setShipmentType(1) + ->setWeightType(0) + ->setDescription('description') + ->setPrice(99); + +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product_rollback.php new file mode 100644 index 0000000000000..a124661a45bac --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product_rollback.php @@ -0,0 +1,23 @@ +get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('bundle-product', false, null, true); + $productRepository->delete($product); +} catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //Product already removed +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php index 09aca0fe9f454..864d2dfafe53b 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php @@ -3,22 +3,26 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + /* * Since the bundle product creation GUI doesn't allow to choose values for bundled products' custom options, * bundled items should not contain products with required custom options. * However, if to create such a bundle product, it will be always out of stock. */ require __DIR__ . '/../../../Magento/Catalog/_files/products.php'; + /** @var $objectManager \Magento\TestFramework\ObjectManager */ $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ $productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); $sampleProduct = $productRepository->get('simple'); + /** @var $product \Magento\Catalog\Model\Product */ $product = $objectManager->create(\Magento\Catalog\Model\Product::class); $product->setTypeId('bundle') ->setId(3) ->setAttributeSetId(4) + ->setWeight(2) ->setWebsiteIds([1]) ->setName('Bundle Product') ->setSku('bundle-product') @@ -26,8 +30,10 @@ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setPriceView(1) + ->setSkuType(1) + ->setWeightType(1) ->setPriceType(1) - ->setShipmentType(1) + ->setShipmentType(0) ->setPrice(10.0) ->setBundleOptionsData( [ @@ -44,13 +50,16 @@ [ [ 'product_id' => $sampleProduct->getId(), + 'selection_price_value' => 2.75, 'selection_qty' => 1, 'selection_can_change_qty' => 1, 'delete' => '', + ], ], ] ); + if ($product->getBundleOptionsData()) { $options = []; foreach ($product->getBundleOptionsData() as $key => $optionData) { @@ -59,6 +68,7 @@ ->create(['data' => $optionData]); $option->setSku($product->getSku()); $option->setOptionId(null); + $links = []; $bundleLinks = $product->getBundleSelectionsData(); if (!empty($bundleLinks[$key])) { @@ -70,6 +80,7 @@ $linkProduct = $productRepository->getById($linkData['product_id']); $link->setSku($linkProduct->getSku()); $link->setQty($linkData['selection_qty']); + $link->setPrice($linkData['selection_price_value']); if (isset($linkData['selection_can_change_qty'])) { $link->setCanChangeQuantity($linkData['selection_can_change_qty']); } @@ -85,4 +96,5 @@ $extension->setBundleProductOptions($options); $product->setExtensionAttributes($extension); } -$product->save(); + +$productRepository->save($product, true); diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php index 29dd6550e4ece..e65d518af3419 100644 --- a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php +++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php @@ -5,14 +5,14 @@ */ namespace Magento\BundleImportExport\Model\Import\Product\Type; -use Magento\Framework\App\Bootstrap; +use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\ImportExport\Model\Import; /** * @magentoAppArea adminhtml + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class BundleTest extends \PHPUnit\Framework\TestCase +class BundleTest extends \Magento\TestFramework\Indexer\TestCase { /** * Bundle product test Name @@ -41,9 +41,22 @@ class BundleTest extends \PHPUnit\Framework\TestCase */ protected $optionSkuList = ['Simple 1', 'Simple 2', 'Simple 3']; + public static function setUpBeforeClass() + { + $db = Bootstrap::getInstance()->getBootstrap() + ->getApplication() + ->getDbInstance(); + if (!$db->isDbDumpExists()) { + throw new \LogicException('DB dump does not exist.'); + } + $db->restoreFromDbDump(); + + parent::setUpBeforeClass(); + } + protected function setUp() { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->objectManager = Bootstrap::getObjectManager(); $this->model = $this->objectManager->create(\Magento\CatalogImportExport\Model\Import\Product::class); } @@ -122,6 +135,7 @@ public function testBundleImport() /** * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoDbIsolation disabled * @magentoAppArea adminhtml * @return void */ @@ -179,4 +193,12 @@ public function testBundleImportWithMultipleStoreViews(): void } } } + + /** + * teardown + */ + public function tearDown() + { + parent::tearDown(); + } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php index cd312f147c7c7..1d7936d740b8d 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php @@ -323,7 +323,7 @@ public function testDeleteChildren() /** * @magentoDataFixture Magento/Store/_files/second_store.php * @magentoDataFixture Magento/Catalog/_files/categories.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @return void */ public function testCreateSubcategoryWithMultipleStores() diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute.php index f55afc8c6a211..bb7e241d972e5 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute.php @@ -32,8 +32,8 @@ 'is_filterable_in_search' => 0, 'is_used_for_promo_rules' => 0, 'is_html_allowed_on_front' => 1, - 'is_visible_on_front' => 0, - 'used_in_product_listing' => 0, + 'is_visible_on_front' => 1, + 'used_in_product_listing' => 1, 'used_for_sort_by' => 0, 'frontend_label' => ['Drop-Down Attribute'], 'backend_type' => 'varchar', diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php index c39e03773c356..04769401c147e 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php @@ -152,18 +152,19 @@ public function testConfigurableImport($pathToFile, $productName, $optionSkuList } /** + * @magentoDataFixture Magento/Catalog/_files/enable_reindex_schedule.php * @magentoDataFixture Magento/Store/_files/second_store.php * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_attribute.php * @magentoAppArea adminhtml * @magentoAppIsolation enabled - * @return void + * @magentoDbIsolation disabled */ public function testConfigurableImportWithMultipleStores() { $productSku = 'Configurable 1'; $products = [ 'default' => 'Configurable 1', - 'fixture_second_store' => 'Configurable 1 Second Store', + 'fixture_second_store' => 'Configurable 1 Second Store' ]; $filesystem = $this->objectManager->create( \Magento\Framework\Filesystem::class @@ -174,24 +175,26 @@ public function testConfigurableImportWithMultipleStores() \Magento\ImportExport\Model\Import\Source\Csv::class, [ 'file' => __DIR__ . '/../../_files/import_configurable_for_multiple_store_views.csv', - 'directory' => $directory, + 'directory' => $directory ] ); - $errors = $this->model->setSource($source)->setParameters( + $errors = $this->model->setSource( + $source + )->setParameters( [ 'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, - 'entity' => 'catalog_product', + 'entity' => 'catalog_product' ] )->validateData(); - $this->assertTrue($errors->getErrorsCount() === 0); + $this->assertTrue($errors->getErrorsCount() == 0); $this->model->importData(); foreach ($products as $storeCode => $productName) { $store = $this->objectManager->create(\Magento\Store\Model\Store::class); $store->load($storeCode, 'code'); - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); /** @var \Magento\Catalog\Api\Data\ProductInterface $product */ $product = $productRepository->get($productSku, 0, $store->getId()); $this->assertFalse($product->isObjectNew()); @@ -201,36 +204,40 @@ public function testConfigurableImportWithMultipleStores() } /** + * @magentoDataFixture Magento/Catalog/_files/enable_reindex_schedule.php * @magentoDataFixture Magento/Store/_files/second_store.php * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_attribute.php * @magentoDbIsolation disabled * @magentoAppArea adminhtml - * @return void */ public function testConfigurableImportWithStoreSpecifiedMainItem() { - $expectedErrorMessage = 'Product with assigned super attributes should not have specified "store_view_code"' - . ' value'; - $filesystem = $this->objectManager->create( - \Magento\Framework\Filesystem::class - ); - - $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); - $source = $this->objectManager->create( - \Magento\ImportExport\Model\Import\Source\Csv::class, - [ - 'file' => __DIR__ . '/../../_files/import_configurable_for_multiple_store_views_error.csv', - 'directory' => $directory, - ] - ); - $errors = $this->model->setSource($source)->setParameters( - [ - 'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, - 'entity' => 'catalog_product', - ] - )->validateData(); - - $this->assertTrue($errors->getErrorsCount() === 1); - $this->assertEquals($expectedErrorMessage, $errors->getAllErrors()[0]->getErrorMessage()); + { + $expectedErrorMessage = 'Product with assigned super attributes should not have specified "store_view_code"' + . ' value'; + $filesystem = $this->objectManager->create( + \Magento\Framework\Filesystem::class + ); + + $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = $this->objectManager->create( + \Magento\ImportExport\Model\Import\Source\Csv::class, + [ + 'file' => __DIR__ . '/../../_files/import_configurable_for_multiple_store_views_error.csv', + 'directory' => $directory + ] + ); + $errors = $this->model->setSource( + $source + )->setParameters( + [ + 'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, + 'entity' => 'catalog_product' + ] + )->validateData(); + + $this->assertTrue($errors->getErrorsCount() == 1); + $this->assertEquals($expectedErrorMessage, $errors->getAllErrors()[0]->getErrorMessage()); + } } } diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php index 5925eb06500e0..05d9c5d3acb1e 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php @@ -135,6 +135,82 @@ public function testImportData() ); } + /** + * Tests importData() method. + * + * @magentoDataFixture Magento/Customer/_files/import_export/customer.php + * + * @return void + */ + public function testImportDataWithOneAdditionalColumn(): void + { + $source = new \Magento\ImportExport\Model\Import\Source\Csv( + __DIR__ . '/_files/customer_to_import_with_one_additional_column.csv', + $this->directoryWrite + ); + + /** @var $customersCollection \Magento\Customer\Model\ResourceModel\Customer\Collection */ + $customersCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Customer\Model\ResourceModel\Customer\Collection::class + ); + $customersCollection->resetData(); + $customersCollection->clear(); + + $this->_model + ->setParameters(['behavior' => Import::BEHAVIOR_ADD_UPDATE]) + ->setSource($source) + ->validateData() + ->hasToBeTerminated(); + sleep(1); + $this->_model->importData(); + + $customers = $customersCollection->getItems(); + + /** @var $objectManager \Magento\TestFramework\ObjectManager */ + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + $existingCustomer = $objectManager->get(\Magento\Framework\Registry::class) + ->registry('_fixture/Magento_ImportExport_Customer'); + + $updatedCustomer = $customers[$existingCustomer->getId()]; + + $this->assertNotEquals( + $existingCustomer->getFirstname(), + $updatedCustomer->getFirstname(), + 'Firstname must be changed' + ); + + $this->assertNotEquals( + $existingCustomer->getUpdatedAt(), + $updatedCustomer->getUpdatedAt(), + 'Updated at date must be changed' + ); + + $this->assertEquals( + $existingCustomer->getLastname(), + $updatedCustomer->getLastname(), + 'Lastname must not be changed' + ); + + $this->assertEquals( + $existingCustomer->getStoreId(), + $updatedCustomer->getStoreId(), + 'Store Id must not be changed' + ); + + $this->assertEquals( + $existingCustomer->getCreatedAt(), + $updatedCustomer->getCreatedAt(), + 'Creation date must not be changed' + ); + + $this->assertEquals( + $existingCustomer->getCustomerGroupId(), + $updatedCustomer->getCustomerGroupId(), + 'Customer group must not be changed' + ); + } + /** * Test importData() method (delete behavior) * diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customer_to_import_with_one_additional_column.csv b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customer_to_import_with_one_additional_column.csv new file mode 100644 index 0000000000000..fd081e090eb85 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customer_to_import_with_one_additional_column.csv @@ -0,0 +1,2 @@ +email,_website,firstname +CharlesTAlston@teleworm.us,base,Jhon diff --git a/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php b/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php index 7d5cb55ec2f96..5e8239586d76b 100644 --- a/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php +++ b/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php @@ -78,6 +78,19 @@ public function testGetSelectOptions() ); } + /** + * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php + */ + public function testAttributeEntityValueNotSet() + { + $entity = $this->objectManager->create(\Magento\Catalog\Model\Product::class); + $entity->setStoreId(0); + $entity->load(1); + $frontEnd = $this->attribute->loadByCode('catalog_product', 'dropdown_attribute'); + $value = $frontEnd->getFrontend()->getValue($entity); + $this->assertFalse($value); + } + /** * Cache key generation * @return string diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php index d1b22e853ce1d..103df9f570a2a 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php @@ -44,6 +44,9 @@ public function getConstructorArgumentTypesDataProvider() '\Imported\Name\Space\ClassName\Under\Test', '\Imported\Name\Space\ClassName', '\Some\Testing\Name\Space\Test', + '\Exception', + '', + '\Imported\Name\Space\ClassName', 'array', '' ], diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample index 47c059de2034b..5efe7837e7a3a 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample @@ -19,6 +19,9 @@ class AnotherSimpleClass ClassName\Under\Test $itemFive, ClassName $itemSix, Test $itemSeven, + ?\Exception $optionalException, + $defaultString = '$default),value;', + ?ClassName $optionalWithDefaultValue = null, array $itemEight = [], $itemNine = 'test' ) { diff --git a/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php new file mode 100644 index 0000000000000..5b39a2afb9d6c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php @@ -0,0 +1,86 @@ +loadXML($sourceString); + $actual = $this->converter->convert($document); + + self::assertEquals( + $expected, + $actual + ); + } + + /** + * Data provider for testParseVarElement. + * + * @return array + */ + public function parseVarElementDataProvider() + { + $sourceString = <<<'XML' + + + + some string + 1 + 0 + true + false + + +XML; + $expectedResult = [ + 'vars' => [ + 'Magento_Test' => [ + 'str' => 'some string', + 'int-1' => '1', + 'int-0' => '0', + 'bool-true' => true, + 'bool-false' => false + ] + ] + ]; + + return [ + [ + $sourceString, + $expectedResult + ], + ]; + } + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->converter = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Framework\Config\Converter::class); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/TranslateCachingTest.php b/dev/tests/integration/testsuite/Magento/Framework/TranslateCachingTest.php index b05d98133980b..f1f65ec2ada80 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/TranslateCachingTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/TranslateCachingTest.php @@ -48,7 +48,7 @@ public function testLoadDataCaching() /** @var \Magento\Framework\Translate $model */ $model = $this->objectManager->get(\Magento\Framework\Translate::class); - $model->loadData(\Magento\Framework\App\Area::AREA_FRONTEND); // this is supposed to cache the fixture + $model->loadData(\Magento\Framework\App\Area::AREA_FRONTEND, true); // this is supposed to cache the fixture $this->assertEquals('Fixture Db Translation', new Phrase('Fixture String')); /** @var \Magento\Translation\Model\ResourceModel\StringUtils $translateString */ diff --git a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php index 3d717d6282268..f362e75ea790e 100644 --- a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php +++ b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php @@ -19,6 +19,7 @@ * Tests the different cases of consumers running by ConsumersRunner * * {@inheritdoc} + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ConsumersRunnerTest extends \PHPUnit\Framework\TestCase { @@ -104,7 +105,7 @@ protected function setUp() $command = str_replace('bin/magento', 'dev/tests/integration/bin/magento', $command); $command = $params . ' ' . $command; - return exec("{$command} > /dev/null &"); + return exec("{$command} >/dev/null &"); }); } diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php index b1ba88f601cdd..bd641dab26c09 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php @@ -12,6 +12,7 @@ use Magento\Paypal\Model\Config; use Magento\Paypal\Model\Info; use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\ResourceModel\Quote\Collection; use Magento\TestFramework\Helper\Bootstrap; @@ -28,22 +29,22 @@ class CheckoutTest extends \PHPUnit\Framework\TestCase private $objectManager; /** - * @var Info + * @var Info|\PHPUnit_Framework_MockObject_MockObject */ private $paypalInfo; /** - * @var Config + * @var Config|\PHPUnit_Framework_MockObject_MockObject */ private $paypalConfig; /** - * @var Factory + * @var Factory|\PHPUnit_Framework_MockObject_MockObject */ private $apiTypeFactory; /** - * @var Nvp + * @var Nvp|\PHPUnit_Framework_MockObject_MockObject */ private $api; @@ -96,7 +97,7 @@ protected function setUp() */ public function testCheckoutStartWithBillingAddress() { - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $paypalConfig = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); @@ -151,7 +152,7 @@ public function testCheckoutStartWithBillingAddress() public function testPrepareCustomerQuote() { /** @var Quote $quote */ - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $quote->setCheckoutMethod(Onepage::METHOD_CUSTOMER); // to dive into _prepareCustomerQuote() on switch $quote->getShippingAddress()->setSameAsBilling(0); $quote->setReservedOrderId(null); @@ -163,7 +164,7 @@ public function testPrepareCustomerQuote() /** @var \Magento\Customer\Model\Session $customerSession */ $customerSession = $this->objectManager->get(\Magento\Customer\Model\Session::class); $customerSession->loginById(1); - $checkout = $this->_getCheckout($quote); + $checkout = $this->getCheckout($quote); $checkout->place('token'); /** @var \Magento\Customer\Api\CustomerRepositoryInterface $customerService */ @@ -191,12 +192,12 @@ public function testPrepareCustomerQuote() public function testPlaceGuestQuote() { /** @var Quote $quote */ - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $quote->setCheckoutMethod(Onepage::METHOD_GUEST); // to dive into _prepareGuestQuote() on switch $quote->getShippingAddress()->setSameAsBilling(0); $quote->setReservedOrderId(null); - $checkout = $this->_getCheckout($quote); + $checkout = $this->getCheckout($quote); $checkout->place('token'); $this->assertNull($quote->getCustomerId()); @@ -218,7 +219,7 @@ public function testPlaceGuestQuote() * @param Quote $quote * @return Checkout */ - protected function _getCheckout(Quote $quote) + protected function getCheckout(Quote $quote) { return $this->objectManager->create( Checkout::class, @@ -242,7 +243,7 @@ protected function _getCheckout(Quote $quote) */ public function testReturnFromPaypal() { - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $this->checkoutModel = $this->objectManager->create( Checkout::class, [ @@ -252,12 +253,13 @@ public function testReturnFromPaypal() ] ); - $exportedBillingAddress = $this->_getExportedAddressFixture($quote->getBillingAddress()->getData()); + $prefix = 'exported'; + $exportedBillingAddress = $this->getExportedAddressFixture($quote->getBillingAddress()->getData(), $prefix); $this->api->expects($this->any()) ->method('getExportedBillingAddress') ->will($this->returnValue($exportedBillingAddress)); - $exportedShippingAddress = $this->_getExportedAddressFixture($quote->getShippingAddress()->getData()); + $exportedShippingAddress = $this->getExportedAddressFixture($quote->getShippingAddress()->getData(), $prefix); $this->api->expects($this->any()) ->method('getExportedShippingAddress') ->will($this->returnValue($exportedShippingAddress)); @@ -269,7 +271,7 @@ public function testReturnFromPaypal() $this->checkoutModel->returnFromPaypal('token'); $billingAddress = $quote->getBillingAddress(); - $this->assertContains('exported', $billingAddress->getFirstname()); + $this->assertContains($prefix, $billingAddress->getFirstname()); $this->assertEquals('note', $billingAddress->getCustomerNote()); $shippingAddress = $quote->getShippingAddress(); @@ -298,27 +300,25 @@ public function testReturnFromPaypal() */ public function testReturnFromPaypalButton() { - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $this->prepareCheckoutModel($quote); $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 1); $this->checkoutModel->returnFromPaypal('token'); $shippingAddress = $quote->getShippingAddress(); + $prefix = ''; - $prefix = 'exported'; $this->assertEquals([$prefix . $this->getExportedData()['street']], $shippingAddress->getStreet()); $this->assertEquals($prefix . $this->getExportedData()['firstname'], $shippingAddress->getFirstname()); $this->assertEquals($prefix . $this->getExportedData()['city'], $shippingAddress->getCity()); $this->assertEquals($prefix . $this->getExportedData()['telephone'], $shippingAddress->getTelephone()); - // This fields not in exported keys list. Fields the same as quote shipping and billing address. - $this->assertNotEquals($prefix . $this->getExportedData()['region'], $shippingAddress->getRegion()); - $this->assertNotEquals($prefix . $this->getExportedData()['email'], $shippingAddress->getEmail()); + $this->assertEquals($prefix . $this->getExportedData()['email'], $shippingAddress->getEmail()); } /** * The case when handling address data from the checkout. - * System's address fields are not replacing from export Paypal data. + * System's address fields are not replacing from export PayPal data. * * @magentoDataFixture Magento/Paypal/_files/quote_payment_express_with_customer.php * @magentoAppIsolation enabled @@ -326,7 +326,7 @@ public function testReturnFromPaypalButton() */ public function testReturnFromPaypalIfCheckout() { - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $this->prepareCheckoutModel($quote); $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 0); @@ -342,6 +342,96 @@ public function testReturnFromPaypalIfCheckout() $this->assertNotEquals($prefix . $this->getExportedData()['telephone'], $shippingAddress->getTelephone()); } + /** + * Test case when customer doesn't have either billing or shipping addresses. + * Customer add virtual product to quote and place order using PayPal Express method. + * After return from PayPal quote billing address have to be updated by PayPal Express address. + * + * @magentoDataFixture Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php + * @magentoConfigFixture current_store payment/paypal_express/active 1 + * @magentoDbIsolation enabled + * + * @return void + */ + public function testReturnFromPayPalForCustomerWithEmptyAddresses(): void + { + $quote = $this->getFixtureQuote(); + $this->prepareCheckoutModel($quote); + $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 0); + + $this->checkoutModel->returnFromPaypal('token'); + + $billingAddress = $quote->getBillingAddress(); + + $this->performQuoteAddressAssertions($billingAddress, $this->getExportedData()); + } + + /** + * Test case when customer doesn't have either billing or shipping addresses. + * Customer add virtual product to quote and place order using PayPal Express method. + * Default store country is in PayPal Express allowed specific country list. + * + * @magentoDataFixture Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php + * @magentoConfigFixture current_store payment/paypal_express/active 1 + * @magentoConfigFixture current_store payment/paypal_express/allowspecific 1 + * @magentoConfigFixture current_store payment/paypal_express/specificcountry US,GB + * @magentoConfigFixture current_store general/country/default US + * + * @magentoDbIsolation enabled + * + * @return void + */ + public function testPaymentValidationWithAllowedSpecificCountry(): void + { + $quote = $this->getFixtureQuote(); + $this->prepareCheckoutModel($quote); + + $quote->getPayment()->getMethodInstance()->validate(); + } + + /** + * Test case when customer doesn't have either billing or shipping addresses. + * Customer add virtual product to quote and place order using PayPal Express method. + * PayPal Express allowed specific country list doesn't contain default store country. + * + * @magentoDataFixture Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php + * @magentoConfigFixture current_store payment/paypal_express/active 1 + * @magentoConfigFixture current_store payment/paypal_express/allowspecific 1 + * @magentoConfigFixture current_store payment/paypal_express/specificcountry US,GB + * @magentoConfigFixture current_store general/country/default CA + * + * @magentoDbIsolation enabled + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage You can't use the payment type you selected to make payments to the billing country. + * + * @return void + */ + public function testPaymentValidationWithAllowedSpecificCountryNegative(): void + { + $quote = $this->getFixtureQuote(); + $this->prepareCheckoutModel($quote); + $quote->getPayment()->getMethodInstance()->validate(); + } + + /** + * Performs quote address assertions. + * + * @param Address $address + * @param array $expected + * @return void + */ + private function performQuoteAddressAssertions(Address $address, array $expected): void + { + foreach ($expected as $key => $item) { + $methodName = 'get' . ucfirst($key); + if ($key === 'street') { + $item = [$item]; + } + + $this->assertEquals($item, $address->$methodName(), 'The "'. $key . '" does not match.'); + } + } + /** * Initialize a checkout model mock. * @@ -358,18 +448,15 @@ private function prepareCheckoutModel(Quote $quote) ] ); - $exportedBillingAddress = $this->_getExportedAddressFixture($this->getExportedData()); - $this->api->expects($this->any()) - ->method('getExportedBillingAddress') - ->will($this->returnValue($exportedBillingAddress)); + $exportedBillingAddress = $this->getExportedAddressFixture($this->getExportedData()); + $this->api->method('getExportedBillingAddress') + ->willReturn($exportedBillingAddress); - $exportedShippingAddress = $this->_getExportedAddressFixture($this->getExportedData()); - $this->api->expects($this->any()) - ->method('getExportedShippingAddress') - ->will($this->returnValue($exportedShippingAddress)); + $exportedShippingAddress = $this->getExportedAddressFixture($this->getExportedData()); + $this->api->method('getExportedShippingAddress') + ->willReturn($exportedShippingAddress); - $this->paypalInfo->expects($this->once()) - ->method('importToPayment') + $this->paypalInfo->method('importToPayment') ->with($this->api, $quote->getPayment()); } @@ -378,19 +465,20 @@ private function prepareCheckoutModel(Quote $quote) * * @return array */ - private function getExportedData() + private function getExportedData(): array { - return - [ - 'company' => 'Testcompany', - 'email' => 'buyeraccountmpi@gmail.com', - 'firstname' => 'testFirstName', - 'country_id' => 'US', - 'region' => 'testRegion', - 'city' => 'testSity', - 'street' => 'testStreet', - 'telephone' => '223344', - ]; + return [ + 'email' => 'customer@example.com', + 'firstname' => 'John', + 'lastname' => 'Doe', + 'country' => 'US', + 'region' => 'Colorado', + 'region_id' => '13', + 'city' => 'Denver', + 'street' => '66 Pearl St', + 'postcode' => '80203', + 'telephone' => '555-555-555', + ]; } /** @@ -402,7 +490,7 @@ private function getExportedData() */ public function testGuestReturnFromPaypal() { - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $paypalConfig = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); @@ -439,12 +527,12 @@ public function testGuestReturnFromPaypal() ->method('create') ->will($this->returnValue($api)); - $exportedBillingAddress = $this->_getExportedAddressFixture($quote->getBillingAddress()->getData()); + $exportedBillingAddress = $this->getExportedAddressFixture($quote->getBillingAddress()->getData()); $api->expects($this->any()) ->method('getExportedBillingAddress') ->will($this->returnValue($exportedBillingAddress)); - $exportedShippingAddress = $this->_getExportedAddressFixture($quote->getShippingAddress()->getData()); + $exportedShippingAddress = $this->getExportedAddressFixture($quote->getShippingAddress()->getData()); $api->expects($this->any()) ->method('getExportedShippingAddress') ->will($this->returnValue($exportedShippingAddress)); @@ -464,15 +552,27 @@ public function testGuestReturnFromPaypal() * Prepare fixture for exported address. * * @param array $addressData + * @param string $prefix * @return \Magento\Framework\DataObject */ - protected function _getExportedAddressFixture(array $addressData) + private function getExportedAddressFixture(array $addressData, string $prefix = ''): \Magento\Framework\DataObject { - $addressDataKeys = ['firstname', 'lastname', 'street', 'city', 'telephone']; + $addressDataKeys = [ + 'country', + 'firstname', + 'lastname', + 'street', + 'city', + 'telephone', + 'postcode', + 'region', + 'region_id', + 'email', + ]; $result = []; foreach ($addressDataKeys as $key) { if (isset($addressData[$key])) { - $result[$key] = 'exported' . $addressData[$key]; + $result[$key] = $prefix . $addressData[$key]; } } @@ -488,7 +588,7 @@ protected function _getExportedAddressFixture(array $addressData) * * @return Quote */ - protected function _getFixtureQuote() + private function getFixtureQuote(): Quote { /** @var Collection $quoteCollection */ $quoteCollection = $this->objectManager->create(Collection::class); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php new file mode 100644 index 0000000000000..d0c16c2b41f09 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php @@ -0,0 +1,76 @@ +loadArea('adminhtml'); +$objectManager = Bootstrap::getObjectManager(); + +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = $objectManager->create(CustomerRepositoryInterface::class); +$customer = $customerRepository->get('customer@example.com'); + +/** @var $product Product */ +$product = $objectManager->create(Product::class); +$product->isObjectNew(true); +$product->setTypeId(Type::TYPE_VIRTUAL) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Virtual Product') + ->setSku('virtual-product-express') + ->setPrice(10) + ->setWeight(1) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED); + +/** @var StockItemInterface $stockItem */ +$stockItem = $objectManager->create(StockItemInterface::class); +$stockItem->setQty(100) + ->setIsInStock(true); +$extensionAttributes = $product->getExtensionAttributes(); +$extensionAttributes->setStockItem($stockItem); + +/** @var $productRepository ProductRepositoryInterface */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$product = $productRepository->save($product); + +$billingAddress = $objectManager->create(Address::class); +$billingAddress->setAddressType('billing'); + +/** @var $quote Quote */ +$quote = $objectManager->create(Quote::class); +$quote->setCustomerIsGuest(false) + ->setCustomerId($customer->getId()) + ->setCustomer($customer) + ->setStoreId($objectManager->get(StoreManagerInterface::class)->getStore()->getId()) + ->setReservedOrderId('test02') + ->setBillingAddress($billingAddress); +$item = $objectManager->create(Item::class); +$item->setProduct($product) + ->setPrice($product->getPrice()) + ->setQty(1); +$quote->addItem($item); +$quote->getPayment()->setMethod(\Magento\Paypal\Model\Config::METHOD_EXPRESS); + +/** @var CartRepositoryInterface $quoteRepository */ +$quoteRepository = $objectManager->create(CartRepositoryInterface::class); +$quoteRepository->save($quote); +$quote = $quoteRepository->get($quote->getId()); diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/ResourceModel/QuoteTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/ResourceModel/QuoteTest.php deleted file mode 100644 index 3bd22ef29cb23..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/ResourceModel/QuoteTest.php +++ /dev/null @@ -1,45 +0,0 @@ -_resourceModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Quote\Model\ResourceModel\Quote::class - ); - } - - /** - * Test to verify if isOrderIncrementIdUsed method works with numeric increment ids - * - * @magentoDataFixture Magento/Sales/_files/order.php - */ - public function testIsOrderIncrementIdUsedNumericIncrementId() - { - $this->assertTrue($this->_resourceModel->isOrderIncrementIdUsed('100000001')); - } - - /** - * Test to verify if isOrderIncrementIdUsed method works with alphanumeric increment ids - * - * @magentoDataFixture Magento/Sales/_files/order_alphanumeric_id.php - */ - public function testIsOrderIncrementIdUsedAlphanumericIncrementId() - { - $this->assertTrue($this->_resourceModel->isOrderIncrementIdUsed('M00000001')); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/ItemRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/ItemRepositoryTest.php new file mode 100644 index 0000000000000..8f0c255a903b9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/ItemRepositoryTest.php @@ -0,0 +1,54 @@ +order = $objectManager->create(\Magento\Sales\Model\Order::class); + $this->orderItemRepository = $objectManager->create(\Magento\Sales\Api\OrderItemRepositoryInterface::class); + $this->searchCriteriaBuilder = $objectManager->create(\Magento\Framework\Api\SearchCriteriaBuilder::class); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order_configurable_product.php + */ + public function testAddOrderItemParent() + { + $this->order->load('100000001', 'increment_id'); + + foreach ($this->order->getItems() as $item) { + if ($item->getProductType() === \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) { + $orderItem = $this->orderItemRepository->get($item->getItemId()); + $this->assertInstanceOf(\Magento\Sales\Api\Data\OrderItemInterface::class, $orderItem->getParentItem()); + } + } + + $itemList = $this->orderItemRepository->getList( + $this->searchCriteriaBuilder->addFilter('order_id', $this->order->getId())->create() + ); + + foreach ($itemList->getItems() as $item) { + if ($item->getProductType() === \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) { + $this->assertInstanceOf(\Magento\Sales\Api\Data\OrderItemInterface::class, $item->getParentItem()); + } + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/OrderIncrementIdCheckerTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/OrderIncrementIdCheckerTest.php new file mode 100644 index 0000000000000..6111c73038f7c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/OrderIncrementIdCheckerTest.php @@ -0,0 +1,51 @@ +checker = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Sales\Model\OrderIncrementIdChecker::class + ); + } + + /** + * Test to verify if isIncrementIdUsed method works with numeric increment ids. + * + * @magentoDataFixture Magento/Sales/_files/order.php + * @return void + */ + public function testIsOrderIncrementIdUsedNumericIncrementId(): void + { + $this->assertTrue($this->checker->isIncrementIdUsed('100000001')); + } + + /** + * Test to verify if isIncrementIdUsed method works with alphanumeric increment ids. + * + * @magentoDataFixture Magento/Sales/_files/order_alphanumeric_id.php + * @return void + */ + public function testIsOrderIncrementIdUsedAlphanumericIncrementId(): void + { + $this->assertTrue($this->checker->isIncrementIdUsed('M00000001')); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php new file mode 100644 index 0000000000000..e601e2dd59232 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php @@ -0,0 +1,123 @@ +cartManagement = Bootstrap::getObjectManager()->create(GuestCartManagementInterface::class); + $this->couponManagement = Bootstrap::getObjectManager()->create(GuestCouponManagementInterface::class); + $this->cartItemRepository = Bootstrap::getObjectManager()->create(GuestCartItemRepositoryInterface::class); + } + + /** + * Applies fixed discount amount on whole cart. + * + * @param array $productPrices + * @return void + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/SalesRule/_files/coupon_cart_fixed_discount.php + * @dataProvider applyFixedDiscountDataProvider + */ + public function testApplyFixedDiscount(array $productPrices): void + { + $expectedDiscount = '-15.00'; + $couponCode = 'CART_FIXED_DISCOUNT_15'; + $cartId = $this->cartManagement->createEmptyCart(); + + foreach ($productPrices as $price) { + $product = $this->createProduct($price); + + /** @var CartItemInterface $quoteItem */ + $quoteItem = Bootstrap::getObjectManager()->create(CartItemInterface::class); + $quoteItem->setQuoteId($cartId); + $quoteItem->setProduct($product); + $quoteItem->setQty(1); + $this->cartItemRepository->save($quoteItem); + } + + $this->couponManagement->set($cartId, $couponCode); + + /** @var GuestCartTotalRepositoryInterface $cartTotalRepository */ + $cartTotalRepository = Bootstrap::getObjectManager()->get(GuestCartTotalRepositoryInterface::class); + $total = $cartTotalRepository->get($cartId); + + $this->assertEquals($expectedDiscount, $total->getBaseDiscountAmount()); + } + + /** + * @return array + */ + public function applyFixedDiscountDataProvider(): array + { + return [ + 'prices when discount had wrong value 15.01' => [[22, 14, 43, 7.50, 0.00]], + 'prices when discount had wrong value 14.99' => [[47, 33, 9.50, 42, 0.00]], + ]; + } + + /** + * Returns simple product with given price. + * + * @param float $price + * @return ProductInterface + */ + private function createProduct(float $price): ProductInterface + { + $name = 'simple-' . $price; + $productRepository = Bootstrap::getObjectManager()->get(ProductRepository::class); + $product = Bootstrap::getObjectManager()->create(Product::class); + $product->setTypeId('simple') + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([1]) + ->setName($name) + ->setSku(uniqid($name)) + ->setPrice($price) + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData(['qty' => 1, 'is_in_stock' => 1]) + ->setWeight(1); + + return $productRepository->save($product); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount.php new file mode 100644 index 0000000000000..08e3ffe6e046c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount.php @@ -0,0 +1,49 @@ +create(Rule::class); +$salesRule->setData( + [ + 'name' => '15$ fixed discount on whole cart', + 'is_active' => 1, + 'customer_group_ids' => [GroupManagement::NOT_LOGGED_IN_ID], + 'coupon_type' => Rule::COUPON_TYPE_SPECIFIC, + 'conditions' => [ + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Address::class, + 'attribute' => 'base_subtotal', + 'operator' => '>', + 'value' => 45, + ], + ], + 'simple_action' => Rule::CART_FIXED_ACTION, + 'discount_amount' => 15, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + 'website_ids' => [ + $objectManager->get(StoreManagerInterface::class)->getWebsite()->getId(), + ], + ] +); +$objectManager->get(\Magento\SalesRule\Model\ResourceModel\Rule::class)->save($salesRule); + +// Create coupon and assign "15$ fixed discount" rule to this coupon. +$coupon = $objectManager->create(Coupon::class); +$coupon->setRuleId($salesRule->getId()) + ->setCode('CART_FIXED_DISCOUNT_15') + ->setType(0); +$objectManager->get(CouponRepositoryInterface::class)->save($coupon); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/paypal/button.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/paypal/button.test.js index a4767fb551ee3..5614a9a1bc6e1 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/paypal/button.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/paypal/button.test.js @@ -64,9 +64,17 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + afterAll(function (done) { tplElement.remove(); registry.remove(component.integrationName); + done(); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js index 52739eec2782b..84db685c02987 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js @@ -56,6 +56,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + it('Check if payment code and message container are restored after onActiveChange call.', function () { var expectedMessageContainer = braintreeCcForm.messageContainer, expectedCode = braintreeCcForm.code; diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js index a9987f5e01ba8..596017be9a33c 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js @@ -64,6 +64,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + it('The PayPal::initAuthFlow throws an exception.', function () { spyOn(additionalValidator, 'validate').and.returnValue(true); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/final-price.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/final-price.test.js index 1e3e066998d09..13b68aa4d9ea7 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/final-price.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/final-price.test.js @@ -28,6 +28,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/list/columns/final-price', function () { describe('"getPrice" method', function () { it('Check returned value', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/image.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/image.test.js index 90ca32fcf8c23..776677ea04f11 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/image.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/image.test.js @@ -37,6 +37,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/list/columns/image', function () { var image = { url: 'url', diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/breadcrumbs.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/breadcrumbs.test.js index 6ef6bb5c52769..b0516af46bc6b 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/breadcrumbs.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/breadcrumbs.test.js @@ -48,6 +48,13 @@ define([ ); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/breadcrumbs', function () { it('mixin is applied to Magento_Theme/js/view/breadcrumbs', function () { var breadcrumbMixins = defaultContext.config.config.mixins['Magento_Theme/js/view/breadcrumbs']; diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js index 2ecfe41f29c13..fdd0b70997f35 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js @@ -44,6 +44,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/storage/data-storage', function () { describe('"initCustomerDataInvalidateListener" method', function () { it('check returned value', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage-compare.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage-compare.test.js index da162a2e9cf4f..aa68c498e0fea 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage-compare.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage-compare.test.js @@ -28,6 +28,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/storage/ids-storage-compare', function () { describe('"providerDataHandler" method', function () { it('check calls "prepareData" and "add" method', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js index e4d3bd62a862d..c394a2463f5e2 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js @@ -22,6 +22,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/storage/ids-storage', function () { describe('"getDataFromLocalStorage" method', function () { it('check calls localStorage get method', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js index 5530666c7ed70..4ef1aa2a98976 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js @@ -29,6 +29,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/storage/storage-service', function () { var config = { namespace: 'namespace', diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/storage-manager.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/storage-manager.test.js index 4514684edf116..571113f481bf3 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/storage-manager.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/storage-manager.test.js @@ -36,6 +36,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/storage-manager', function () { describe('"initStorages" method', function () { beforeEach(function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/redirect-on-success.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/redirect-on-success.test.js index 8961d99170dcd..4b053b4322dea 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/redirect-on-success.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/redirect-on-success.test.js @@ -27,6 +27,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + it('Checks if loader is called before redirect to success page.', function () { spyOn(window.location, 'replace').and.returnValue(false); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/cache.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/cache.test.js index c796bd4f9f2ec..7f905a91048fa 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/cache.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/cache.test.js @@ -30,6 +30,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/model/cart/cache', function () { describe('Check the "get" method', function () { it('Check default call with "cart-data" key', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js index 216ad28cf496a..3d8325a3ecd54 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js @@ -65,6 +65,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/model/cart/estimate-service', function () { it('test subscribe when billingAddress was changed for virtual quote', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js index 44f06279dcbef..47518bef19e56 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js @@ -89,6 +89,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/model/cart/totals-processor/default', function () { it('estimateTotals if data was cached', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/cart/shipping-estimation.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/cart/shipping-estimation.test.js index 5e6dbfa5c0372..c1853d228c104 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/cart/shipping-estimation.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/cart/shipping-estimation.test.js @@ -53,6 +53,13 @@ define(['squire', 'ko'], function (Squire, ko) { }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/view/cart/shipping-estimation', function () { describe('"initElement" method', function () { it('Check for return value and element that initiated.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/minicart.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/minicart.test.js index 570de61052309..4f3949bd8124e 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/minicart.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/minicart.test.js @@ -41,6 +41,13 @@ define(['squire'], function (Squire) { }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/view/minicart', function () { describe('"getCartItems" method', function () { it('Check for return value.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js index 5f14bc28f7995..ff420c4830476 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js @@ -77,6 +77,13 @@ define(['squire', 'ko', 'jquery', 'jquery/validate'], function (Squire, ko, $) { }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/view/shipping', function () { describe('"navigate" method', function () { it('Check for return value.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/summary/cart-items.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/summary/cart-items.test.js index d1698b2fedac3..d64f07ab619ba 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/summary/cart-items.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/summary/cart-items.test.js @@ -68,6 +68,13 @@ define(['squire'], function (Squire) { }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/view/summary/cart-items', function () { describe('"getItemsQty" method', function () { it('Check for return value.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/agreement-validator.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/agreement-validator.test.js index afff4660cab41..ef33d49bf11d6 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/agreement-validator.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/agreement-validator.test.js @@ -36,6 +36,11 @@ define(['squire', 'jquery'], function (Squire, $) { afterEach(function () { $('.payment-method._active').remove(); + + try { + injector.clean(); + injector.remove(); + } catch (e) {} }); describe('Magento_CheckoutAgreements/js/model/agreement-validator', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/place-order-mixin.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/place-order-mixin.test.js index 545daf0a330c9..1e3cf3e0280b6 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/place-order-mixin.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/place-order-mixin.test.js @@ -34,6 +34,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_CheckoutAgreements/js/model/place-order-mixin', function () { it('mixin is applied to Magento_Checkout/js/action/place-order', function () { var placeOrderMixins = defaultContext.config.config.mixins['Magento_Checkout/js/action/place-order']; diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/set-payment-information-mixin.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/set-payment-information-mixin.test.js index ed525bfd96a6c..8ea07b78b02c9 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/set-payment-information-mixin.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/set-payment-information-mixin.test.js @@ -34,6 +34,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_CheckoutAgreements/js/model/set-payment-information-mixin', function () { it('mixin is applied to Magento_Checkout/js/action/set-payment-information', function () { var placeOrderMixins = defaultContext diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/invalidation-processor.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/invalidation-processor.test.js index 9067eb5d7fc71..4646eca268ec1 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/invalidation-processor.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/invalidation-processor.test.js @@ -29,6 +29,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Customer/js/invalidation-processor', function () { describe('"process" method', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/view/authentication-popup.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/view/authentication-popup.test.js index 3004d0266f5af..8a8216a820dd4 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/view/authentication-popup.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/view/authentication-popup.test.js @@ -44,6 +44,13 @@ define(['squire'], function (Squire) { }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Customer/js/view/authentication-popup', function () { describe('"isActive" method', function () { it('Check for return value.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/InstantPurchase/frontend/web/js/view/instant-purchase.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/InstantPurchase/frontend/web/js/view/instant-purchase.test.js index 979e0dd351ee6..b61742b55fa2f 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/InstantPurchase/frontend/web/js/view/instant-purchase.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/InstantPurchase/frontend/web/js/view/instant-purchase.test.js @@ -41,6 +41,11 @@ define([ afterEach(function () { $('#product_addtocart_form').remove(); + + try { + injector.clean(); + injector.remove(); + } catch (e) {} }); it('Check initialized data.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/in-context/express-checkout.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/in-context/express-checkout.test.js index 7a425e479b93b..0420256e20ef5 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/in-context/express-checkout.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/in-context/express-checkout.test.js @@ -51,6 +51,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('clientConfig.click method', function () { it('Check for properties defined ', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js index 8b942818168cb..395dd96822ea5 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js @@ -58,6 +58,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('"click" method checks', function () { it('check success request', function () { mocks['Magento_Paypal/js/action/set-payment-method'].and.callFake(function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js index 47e3507ea1321..f05338ad1e7d2 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js @@ -72,6 +72,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + it('showAcceptanceWindow is invoked when the anchor element of help link is clicked', function (done) { spyOn(paypalExpressAbstract, 'showAcceptanceWindow'); setTimeout(function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/grand-total.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/grand-total.test.js index 4c78bd840d63e..2174c4a913ad0 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/grand-total.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/grand-total.test.js @@ -35,6 +35,13 @@ define(['squire', 'ko'], function (Squire, ko) { }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Tax/js/view/checkout/summary/grand-total', function () { describe('"getGrandTotalExclTax" method', function () { it('Check if totals object empty.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Theme/view/frontend/web/js/view/add-home-breadcrumb.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Theme/view/frontend/web/js/view/add-home-breadcrumb.test.js index 4f74905d1b3a3..9c7bbc161ac4a 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Theme/view/frontend/web/js/view/add-home-breadcrumb.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Theme/view/frontend/web/js/view/add-home-breadcrumb.test.js @@ -37,6 +37,11 @@ define([ afterEach(function () { delete window.BASE_URL; + + try { + injector.clean(); + injector.remove(); + } catch (e) {} }); it('mixin is applied to Magento_Theme/js/view/breadcrumbs', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js index 165cc1aff3ac5..d443784efe050 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js @@ -45,6 +45,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Ui/js/form/element/multiselect', function () { describe('"setPrepareToSendData" method', function () { it('Check method call with empty array as parameter.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/timeline/timeline.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/timeline/timeline.test.js index 12354713400df..c82ca680336b4 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/timeline/timeline.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/timeline/timeline.test.js @@ -35,6 +35,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Ui/js/timeline/timeline', function () { describe('isActive method', function () { diff --git a/dev/tests/js/jasmine/tests/lib/mage/backend/bootstrap.test.js b/dev/tests/js/jasmine/tests/lib/mage/backend/bootstrap.test.js index 834a6df75843f..396b62df0dfe9 100644 --- a/dev/tests/js/jasmine/tests/lib/mage/backend/bootstrap.test.js +++ b/dev/tests/js/jasmine/tests/lib/mage/backend/bootstrap.test.js @@ -19,6 +19,14 @@ define([ done(); }); }); + + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('"sendPostponeRequest" method', function () { it('should insert "Error" notification if request failed', function (done) { jQuery('').appendTo('body'); diff --git a/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js b/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js index ff510046e5528..39fb5330a038a 100644 --- a/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js +++ b/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js @@ -22,41 +22,120 @@ define([ Constr.prototype = wysiwygAdapter; obj = new Constr(); - obj.initialize('id', { - 'directives_url': 'http://example.com/admin/cms/wysiwyg/directive/' - }); }); - describe('wysiwygAdapter', function () { - var decodedHtml = '' + - '', - encodedHtml = '' + - '' + - '', - encodedHtmlWithForwardSlashInImgSrc = encodedHtml.replace('%2C%2C', '%2C%2C/'); - - describe('"encodeDirectives" method', function () { - it('converts media directive img src to directive URL', function () { - expect(obj.encodeDirectives(decodedHtml)).toEqual(encodedHtml); + describe('wysiwygAdapter - encoding and decoding directives', function () { + + /** + * Tests encoding and decoding directives + * + * @param {String} decodedHtml + * @param {String} encodedHtml + */ + function runTests(decodedHtml, encodedHtml) { + var encodedHtmlWithForwardSlashInImgSrc = encodedHtml.replace(/src="((?:(?!"|\\\?).)*)/, 'src="$1/'); + + describe('"encodeDirectives" method', function () { + it('converts media directive img src to directive URL', function () { + expect(obj.encodeDirectives(decodedHtml)).toEqual(encodedHtml); + }); }); + + describe('"decodeDirectives" method', function () { + it( + 'converts directive URL img src without a trailing forward slash ' + + 'to media url without a trailing forward slash', + function () { + expect(obj.decodeDirectives(encodedHtml)).toEqual(decodedHtml); + } + ); + + it('converts directive URL img src with a trailing forward slash ' + + 'to media url without a trailing forward slash', + function () { + expect(encodedHtmlWithForwardSlashInImgSrc).not.toEqual(encodedHtml); + expect(obj.decodeDirectives(encodedHtmlWithForwardSlashInImgSrc)).toEqual(decodedHtml); + } + ); + }); + } + + describe('without SID in directive query string without secret key', function () { + var decodedHtml = '' + + '', + encodedHtml = '' + + '' + + ''; + + beforeEach(function () { + obj.initialize('id', { + 'directives_url': 'http://example.com/admin/cms/wysiwyg/directive/' + }); + }); + + runTests(decodedHtml, encodedHtml); }); - describe('"decodeDirectives" method', function () { - it( - 'converts directive URL img src without a trailing forward slash ' + - 'to media url without a trailing forward slash', - function () { - expect(obj.decodeDirectives(encodedHtml)).toEqual(decodedHtml); - } - ); - - it('converts directive URL img src with a trailing forward slash ' + - 'to media url without a trailing forward slash', - function () { - expect(obj.decodeDirectives(encodedHtmlWithForwardSlashInImgSrc)).toEqual(decodedHtml); - } - ); + describe('without SID in directive query string with secret key', function () { + var decodedHtml = '' + + '', + encodedHtml = '' + + '' + + '', + directiveUrl = 'http://example.com/admin/cms/wysiwyg/directive/key/' + + '5552655d13a141099d27f5d5b0c58869423fd265687167da12cad2bb39aa9a58/'; + + beforeEach(function () { + obj.initialize('id', { + 'directives_url': directiveUrl + }); + }); + + runTests(decodedHtml, encodedHtml); + }); + + describe('with SID in directive query string without secret key', function () { + var decodedHtml = '' + + '', + encodedHtml = '' + + '' + + '', + directiveUrl = 'http://example.com/admin/cms/wysiwyg/directive?SID=something'; + + beforeEach(function () { + obj.initialize('id', { + 'directives_url': directiveUrl + }); + }); + + runTests(decodedHtml, encodedHtml); + }); + + describe('with SID in directive query string with secret key', function () { + var decodedHtml = '' + + '', + encodedHtml = '' + + '' + + '', + directiveUrl = 'http://example.com/admin/cms/wysiwyg/directive/key/' + + '5552655d13a141099d27f5d5b0c58869423fd265687167da12cad2bb39aa9a58?SID=something'; + + beforeEach(function () { + obj.initialize('id', { + 'directives_url': directiveUrl + }); + }); + + runTests(decodedHtml, encodedHtml); }); }); }); diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php index ee91c34002f6c..8b811cc6b3fc0 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php @@ -6,7 +6,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreStart +// @codingStandardsIgnoreFile return [ ['__get', 'Magento\Framework\DataObject'], @@ -2567,4 +2567,5 @@ 'configure', 'Magento\Framework\MessageQueue\ConsumerInterface' ], + ['isOrderIncrementIdUsed', 'Magento\Quote\Model\ResourceModel\Quote', 'Magento\Sales\Model\OrderIncrementIdChecker::isIncrementIdUsed'], ]; diff --git a/dev/tests/static/testsuite/Magento/Test/Less/_files/whitelist/common.txt b/dev/tests/static/testsuite/Magento/Test/Less/_files/whitelist/common.txt index 4e4fbefd7871d..60f6f9b105ee2 100644 --- a/dev/tests/static/testsuite/Magento/Test/Less/_files/whitelist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Less/_files/whitelist/common.txt @@ -1,2 +1,2 @@ -theme * / -library * / \ No newline at end of file +# Format: or simply +* * / diff --git a/lib/internal/Magento/Framework/Api/Test/Unit/SearchCriteria/CollectionProcessor/SortingProcessorTest.php b/lib/internal/Magento/Framework/Api/Test/Unit/SearchCriteria/CollectionProcessor/SortingProcessorTest.php index 01b272a9e911a..d8d4e48047404 100644 --- a/lib/internal/Magento/Framework/Api/Test/Unit/SearchCriteria/CollectionProcessor/SortingProcessorTest.php +++ b/lib/internal/Magento/Framework/Api/Test/Unit/SearchCriteria/CollectionProcessor/SortingProcessorTest.php @@ -76,15 +76,25 @@ public function testProcess() ->method('getDirection') ->willReturn($orderThreeDirection); + /** @var SortOrder|\PHPUnit_Framework_MockObject_MockObject $sortOrderThreeMock */ + $sortOrderFourMock = $this->getMockBuilder(SortOrder::class) + ->disableOriginalConstructor() + ->getMock(); + $sortOrderFourMock->expects($this->once()) + ->method('getField') + ->willReturn(null); + $sortOrderFourMock->expects($this->never()) + ->method('getDirection'); + /** @var SearchCriteriaInterface|\PHPUnit_Framework_MockObject_MockObject $searchCriteriaMock */ $searchCriteriaMock = $this->getMockBuilder(SearchCriteriaInterface::class) ->getMock(); $searchCriteriaMock->expects($this->exactly(2)) ->method('getSortOrders') - ->willReturn([$sortOrderOneMock, $sortOrderTwoMock, $sortOrderThreeMock]); + ->willReturn([$sortOrderOneMock, $sortOrderTwoMock, $sortOrderThreeMock, $sortOrderFourMock]); - /** @var AbstractDb|\PHPUnit_Framework_MockObject_MockObject $searchCriteriarMock */ + /** @var AbstractDb|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ $collectionMock = $this->getMockBuilder(AbstractDb::class) ->disableOriginalConstructor() ->getMock(); @@ -130,7 +140,7 @@ public function testProcessWithDefaults() ->method('getSortOrders') ->willReturn([]); - /** @var AbstractDb|\PHPUnit_Framework_MockObject_MockObject $searchCriteriarMock */ + /** @var AbstractDb|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ $collectionMock = $this->getMockBuilder(AbstractDb::class) ->disableOriginalConstructor() ->getMock(); diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php new file mode 100644 index 0000000000000..1154749a75b94 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -0,0 +1,38 @@ +feeds = $data; + } + + /** + * {@inheritdoc} + */ + public function getFormattedContent() : string + { + return FeedFactory::factory($this->feeds)->export(FeedFactoryInterface::FORMAT_RSS); + } +} diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php new file mode 100644 index 0000000000000..c80ece29f6143 --- /dev/null +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -0,0 +1,78 @@ +objectManager = $objectManger; + $this->logger = $logger; + $this->formats = $formats; + } + + /** + * {@inheritdoc} + */ + public function create(array $data, string $format = FeedFactoryInterface::FORMAT_RSS) : FeedInterface + { + if (!isset($this->formats[$format])) { + throw new \Magento\Framework\Exception\InputException( + new \Magento\Framework\Phrase('The format is not supported') + ); + } + + if (!is_subclass_of($this->formats[$format], \Magento\Framework\App\FeedInterface::class)) { + throw new \Magento\Framework\Exception\InputException( + new \Magento\Framework\Phrase('Wrong format handler type') + ); + } + + try { + return $this->objectManager->create( + $this->formats[$format], + ['data' => $data] + ); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + throw new \Magento\Framework\Exception\RuntimeException( + new \Magento\Framework\Phrase('There has been an error with import'), + $e + ); + } + } +} diff --git a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php new file mode 100644 index 0000000000000..ac53a366bd127 --- /dev/null +++ b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php @@ -0,0 +1,30 @@ +getFileName() || false == $class->hasMethod( '__construct' @@ -46,49 +48,37 @@ public function getConstructorArgumentTypes(\ReflectionClass $class, $inherited ) { return $output; } - $reflectionConstructor = $class->getConstructor(); - $fileContent = file($class->getFileName()); - $availableNamespaces = $this->namespaceResolver->getImportedNamespaces($fileContent); - $availableNamespaces[0] = $class->getNamespaceName(); - $constructorStartLine = $reflectionConstructor->getStartLine() - 1; - $constructorEndLine = $reflectionConstructor->getEndLine(); - $fileContent = array_slice($fileContent, $constructorStartLine, $constructorEndLine - $constructorStartLine); - $source = ' $argument]; - } - unset($argument); - $arguments = array_filter($arguments, function ($token) { - $blacklist = [T_VARIABLE, T_WHITESPACE]; - if (isset($token[0]) && in_array($token[0], $blacklist)) { - return false; + //Reading parameters' types. + $params = $class->getConstructor()->getParameters(); + /** @var string[] $types */ + $types = []; + foreach ($params as $param) { + //For the sake of backward compatibility. + $typeName = ''; + if ($param->isArray()) { + //For the sake of backward compatibility. + $typeName = 'array'; + } else { + try { + $paramClass = $param->getClass(); + if ($paramClass) { + $typeName = '\\' .$paramClass->getName(); + } + } catch (\ReflectionException $exception) { + //If there's a problem loading a class then ignore it and + //just return it's name. + $typeName = '\\' .$param->getType()->getName(); + } } - return true; - }); - $arguments = array_map(function ($element) { - return $element[1]; - }, $arguments); - $arguments = array_values($arguments); - $arguments = implode('', $arguments); - if (empty($arguments)) { - return $output; + $types[] = $typeName; } - $arguments = explode(',', $arguments); - foreach ($arguments as $key => &$argument) { - $argument = $this->removeToken($argument, '='); - $argument = $this->removeToken($argument, '&'); - $argument = $this->namespaceResolver->resolveNamespace($argument, $availableNamespaces); + if (!$types) { + //For the sake of backward compatibility. + $types = [null]; } - unset($argument); - return $arguments; + + return $types; } /** @@ -98,7 +88,7 @@ public function getConstructorArgumentTypes(\ReflectionClass $class, $inherited * @param array $availableNamespaces * @return string * @deprecated 100.2.0 - * @see \Magento\Framework\Code\Reader\NamespaceResolver::resolveNamespace + * @see getConstructorArgumentTypes */ protected function resolveNamespaces($argument, $availableNamespaces) { @@ -111,6 +101,8 @@ protected function resolveNamespaces($argument, $availableNamespaces) * @param string $argument * @param string $token * @return string + * + * @deprecated Not used anymore. */ protected function removeToken($argument, $token) { @@ -127,7 +119,7 @@ protected function removeToken($argument, $token) * @param array $file * @return array * @deprecated 100.2.0 - * @see \Magento\Framework\Code\Reader\NamespaceResolver::getImportedNamespaces + * @see getConstructorArgumentTypes */ protected function getImportedNamespaces(array $file) { diff --git a/lib/internal/Magento/Framework/Config/Converter.php b/lib/internal/Magento/Framework/Config/Converter.php index 0401471f27ea5..3e66f641b8697 100644 --- a/lib/internal/Magento/Framework/Config/Converter.php +++ b/lib/internal/Magento/Framework/Config/Converter.php @@ -103,7 +103,9 @@ protected function parseVarElement(\DOMElement $node) } } if (!count($result)) { - $result = $node->nodeValue; + $result = (strtolower($node->nodeValue) !== 'true' && strtolower($node->nodeValue) !== 'false') + ? $node->nodeValue + : filter_var($node->nodeValue, FILTER_VALIDATE_BOOLEAN); } return $result; } diff --git a/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php b/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php index 8db84c27980ad..a8451e43ade20 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php @@ -353,8 +353,13 @@ public function getElementHtml() $html .= '' . $beforeElementHtml . ''; } - $html .= '_getUiId() . ' value="' . - $this->getEscapedValue() . '" ' . $this->serialize($this->getHtmlAttributes()) . '/>'; + if (is_array($this->getValue())) { + foreach ($this->getValue() as $value) { + $html .= $this->getHtmlForInputByValue($this->_escape($value)); + } + } else { + $html .= $this->getHtmlForInputByValue($this->getEscapedValue()); + } $afterElementJs = $this->getAfterElementJs(); if ($afterElementJs) { @@ -574,4 +579,17 @@ public function isLocked() { return $this->getData($this->lockHtmlAttribute) == 1; } + + /** + * Get input html by sting value. + * + * @param string|null $value + * + * @return string + */ + private function getHtmlForInputByValue($value) + { + return '_getUiId() + . ' value="' . $value . '" ' . $this->serialize($this->getHtmlAttributes()) . '/>'; + } } diff --git a/lib/internal/Magento/Framework/Encryption/Crypt.php b/lib/internal/Magento/Framework/Encryption/Crypt.php index c304cdb23f85e..5f368f0d5b790 100644 --- a/lib/internal/Magento/Framework/Encryption/Crypt.php +++ b/lib/internal/Magento/Framework/Encryption/Crypt.php @@ -75,7 +75,7 @@ public function __construct( $abc = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $initVector = ''; for ($i = 0; $i < $initVectorSize; $i++) { - $initVector .= $abc[rand(0, strlen($abc) - 1)]; + $initVector .= $abc[random_int(0, strlen($abc) - 1)]; } } elseif (false === $initVector) { /* Set vector to zero bytes to not use it */ diff --git a/lib/internal/Magento/Framework/MultiDimensionalIndexer/Alias.php b/lib/internal/Magento/Framework/MultiDimensionalIndexer/Alias.php deleted file mode 100644 index 9b7e2f3dde9b7..0000000000000 --- a/lib/internal/Magento/Framework/MultiDimensionalIndexer/Alias.php +++ /dev/null @@ -1,56 +0,0 @@ - $value])); - } - $this->value = $value; - } - - /** - * @return string One of self::ALIAS_* - */ - public function getValue(): string - { - return $this->value; - } -} diff --git a/lib/internal/Magento/Framework/MultiDimensionalIndexer/AliasFactory.php b/lib/internal/Magento/Framework/MultiDimensionalIndexer/AliasFactory.php deleted file mode 100644 index aa65736bca817..0000000000000 --- a/lib/internal/Magento/Framework/MultiDimensionalIndexer/AliasFactory.php +++ /dev/null @@ -1,40 +0,0 @@ -objectManager = $objectManager; - } - - /** - * @param array $arguments - * @return Alias - */ - public function create(array $arguments = []): Alias - { - return $this->objectManager->create(Alias::class, $arguments); - } -} diff --git a/lib/internal/Magento/Framework/MultiDimensionalIndexer/Dimension.php b/lib/internal/Magento/Framework/MultiDimensionalIndexer/Dimension.php deleted file mode 100644 index 7cfafcf8c704b..0000000000000 --- a/lib/internal/Magento/Framework/MultiDimensionalIndexer/Dimension.php +++ /dev/null @@ -1,19 +0,0 @@ -objectManager = $objectManager; - } - - /** - * @param array $arguments - * @return Dimension - */ - public function create(array $arguments = []): Dimension - { - return $this->objectManager->create(Dimension::class, $arguments); - } -} diff --git a/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexHandlerInterface.php b/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexHandlerInterface.php deleted file mode 100644 index 8a0eb364d2003..0000000000000 --- a/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexHandlerInterface.php +++ /dev/null @@ -1,36 +0,0 @@ -indexId = $indexId; - $this->dimensions = $dimensions; - $this->alias = $alias; - } - - /** - * @return string - */ - public function getIndexId(): string - { - return $this->indexId; - } - - /** - * @return Dimension[] - */ - public function getDimensions(): array - { - return $this->dimensions; - } - - /** - * @return Alias - */ - public function getAlias(): Alias - { - return $this->alias; - } -} diff --git a/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexNameBuilder.php b/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexNameBuilder.php deleted file mode 100644 index 901b3d51d3737..0000000000000 --- a/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexNameBuilder.php +++ /dev/null @@ -1,112 +0,0 @@ -objectManager = $objectManager; - $this->dimensionFactory = $dimensionFactory; - $this->aliasFactory = $aliasFactory; - } - - /** - * @param string $indexId - * @return self - */ - public function setIndexId(string $indexId): self - { - $this->data[self::$indexId] = $indexId; - return $this; - } - - /** - * @param string $name - * @param string $value - * @return self - */ - public function addDimension(string $name, string $value): self - { - $this->data[self::$dimensions][] = $this->dimensionFactory->create([ - 'name' => $name, - 'value' => $value, - ]); - return $this; - } - - /** - * @param string $alias - * @return self - */ - public function setAlias(string $alias): self - { - $this->data[self::$alias] = $this->aliasFactory->create(['value' => $alias]); - return $this; - } - - /** - * @return IndexName - */ - public function build(): IndexName - { - $indexName = $this->objectManager->create(IndexName::class, $this->data); - $this->data = []; - return $indexName; - } -} diff --git a/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexNameResolver.php b/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexNameResolver.php deleted file mode 100644 index c96c78b976517..0000000000000 --- a/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexNameResolver.php +++ /dev/null @@ -1,59 +0,0 @@ -indexScopeResolver = $indexScopeResolver; - } - - /** - * @inheritdoc - */ - public function resolveName(IndexName $indexName): string - { - $tableName = $this->indexScopeResolver->resolve($indexName->getIndexId(), $indexName->getDimensions()); - - if ($indexName->getAlias()->getValue() === Alias::ALIAS_REPLICA) { - $tableName = $this->getAdditionalTableName($tableName); - } - return $tableName; - } - - /** - * @param string $tableName - * @return string - */ - public function getAdditionalTableName(string $tableName): string - { - return $tableName . $this->additionalTableSuffix; - } -} diff --git a/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexNameResolverInterface.php b/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexNameResolverInterface.php deleted file mode 100644 index 9068acd3373f6..0000000000000 --- a/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexNameResolverInterface.php +++ /dev/null @@ -1,22 +0,0 @@ -resourceConnection = $resourceConnection; - $this->indexNameResolver = $indexNameResolver; - } - - /** - * @inheritdoc - */ - public function switch(IndexName $indexName, string $connectionName) - { - $connection = $this->resourceConnection->getConnection($connectionName); - $tableName = $this->indexNameResolver->resolveName($indexName); - - $this->switchTable($connection, [$tableName]); - } - - /** - * Switch index tables from replica to active - * - * @param AdapterInterface $connection - * @param array $tableNames - * @return void - */ - private function switchTable(AdapterInterface $connection, array $tableNames) - { - $toRename = []; - foreach ($tableNames as $tableName) { - $outdatedTableName = $tableName . $this->outdatedTableSuffix; - $replicaTableName = $tableName . $this->replicaTableSuffix; - - $renameBatch = [ - [ - 'oldName' => $tableName, - 'newName' => $outdatedTableName, - ], - [ - 'oldName' => $replicaTableName, - 'newName' => $tableName, - ], - [ - 'oldName' => $outdatedTableName, - 'newName' => $replicaTableName, - ] - ]; - $toRename = array_merge($toRename, $renameBatch); - } - - if (!empty($toRename)) { - $connection->renameTablesBatch($toRename); - } - } -} diff --git a/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexTableSwitcherInterface.php b/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexTableSwitcherInterface.php deleted file mode 100644 index 6f067dbf6a44b..0000000000000 --- a/lib/internal/Magento/Framework/MultiDimensionalIndexer/IndexTableSwitcherInterface.php +++ /dev/null @@ -1,23 +0,0 @@ -element = $objectManager->getObject(\Magento\Framework\Data\Form\Element\Hidden::class); + } + + /** + * @param mixed $value + * + * @dataProvider getElementHtmlDataProvider + */ + public function testGetElementHtml($value) + { + $form = $this->createMock(\Magento\Framework\Data\Form::class); + $this->element->setForm($form); + $this->element->setValue($value); + $html = $this->element->getElementHtml(); + + if (is_array($value)) { + foreach ($value as $item) { + $this->assertContains($item, $html); + } + return; + } + $this->assertContains($value, $html); + } + + public function getElementHtmlDataProvider() + { + return [ + ['some_value'], + ['store_ids[]' => ['1', '2']], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Test/Unit/MultiDimensionalIndexer/IndexTableSwitcherTest.php b/lib/internal/Magento/Framework/Test/Unit/MultiDimensionalIndexer/IndexTableSwitcherTest.php deleted file mode 100644 index 3a4e59275d1bf..0000000000000 --- a/lib/internal/Magento/Framework/Test/Unit/MultiDimensionalIndexer/IndexTableSwitcherTest.php +++ /dev/null @@ -1,98 +0,0 @@ -indexName = $this->createMock(IndexName::class); - $this->resourceConnection = $this->createMock(ResourceConnection::class); - $this->indexNameResolver = $this->createMock(IndexNameResolverInterface::class); - $this->adapter = $this->createMock(AdapterInterface::class); - - $this->indexTableSwitcher = $objectManager->getObject( - IndexTableSwitcher::class, - [ - 'resourceConnection' => $this->resourceConnection, - 'indexNameResolver' => $this->indexNameResolver, - ] - ); - } - - public function testSwitch() - { - $connectionName = 'testConnection'; - $tableName = 'some_table_name'; - $toRename = - [ - [ - 'oldName' => $tableName, - 'newName' => $tableName . '_outdated', - ], - [ - 'oldName' => $tableName . '_replica', - 'newName' => $tableName, - ], - [ - 'oldName' => $tableName . '_outdated', - 'newName' => $tableName . '_replica', - ], - ]; - - $this->resourceConnection->expects($this->once())->method('getConnection') - ->with($connectionName)->willReturn($this->adapter); - $this->indexNameResolver->expects($this->once())->method('resolveName') - ->with($this->indexName)->willReturn($tableName); - $this->adapter->expects($this->once())->method('renameTablesBatch') - ->with($toRename); - - $this->indexTableSwitcher->switch($this->indexName, $connectionName); - } -} diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/FulltextFilter.php b/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/FulltextFilter.php index e7cbc5000d337..f683e248aec91 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/FulltextFilter.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/FulltextFilter.php @@ -62,6 +62,16 @@ function ($column) use ($alias) { return $columns; } + /** + * Escape against value + * @param string $value + * @return string + */ + private function escapeAgainstValue(string $value): string + { + return preg_replace('/([+\-><\(\)~*\"@]+)/', ' ', $value); + } + /** * Apply fulltext filters * @@ -86,7 +96,7 @@ public function apply(Collection $collection, Filter $filter) $collection->getSelect() ->where( 'MATCH(' . implode(',', $columns) . ') AGAINST(?)', - $filter->getValue() + $this->escapeAgainstValue($filter->getValue()) ); } } diff --git a/lib/internal/Magento/Framework/View/Layout/etc/layout_merged.xsd b/lib/internal/Magento/Framework/View/Layout/etc/layout_merged.xsd index f830f41b53255..e50b8a9cf2808 100644 --- a/lib/internal/Magento/Framework/View/Layout/etc/layout_merged.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/layout_merged.xsd @@ -62,4 +62,10 @@ + + + + + + diff --git a/lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd b/lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd index 397b71fe70825..09ad246b3a64d 100644 --- a/lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd @@ -32,4 +32,10 @@ + + + + + + diff --git a/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php b/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php index 0655bfda0bbc0..b124c417148d4 100644 --- a/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php @@ -317,7 +317,7 @@ public function apiShutdownFunction() protected function _saveFatalErrorReport($reportData) { $this->directoryWrite->create('report/api'); - $reportId = abs(intval(microtime(true) * rand(100, 1000))); + $reportId = abs(intval(microtime(true) * random_int(100, 1000))); $this->directoryWrite->writeFile('report/api/' . $reportId, $this->serializer->serialize($reportData)); return $reportId; } diff --git a/lib/web/css/docs/source/_buttons.less b/lib/web/css/docs/source/_buttons.less index a81599c517ff8..a071eed85ef9e 100644 --- a/lib/web/css/docs/source/_buttons.less +++ b/lib/web/css/docs/source/_buttons.less @@ -397,7 +397,7 @@ button { // # Button with an icon on the left or right side of the text // -// @_button-icon-font-position variable is used to set up the icon postiton +// @_button-icon-font-position variable is used to set up the icon position // // Use before to set up button icon position on the left: // ```css @@ -818,7 +818,7 @@ button { // # Link as a button // -// The .lib-link-as-button() mixin is used to reset link styles (text-decoration and display inline). Then to make it look like a button, you need to add the .lib-button() mixin with appropriate paremeters. +// The .lib-link-as-button() mixin is used to reset link styles (text-decoration and display inline). Then to make it look like a button, you need to add the .lib-button() mixin with appropriate parameters. // // ```html // Button diff --git a/lib/web/css/docs/source/_layout.less b/lib/web/css/docs/source/_layout.less index 37598197142f6..799f940b44c02 100644 --- a/lib/web/css/docs/source/_layout.less +++ b/lib/web/css/docs/source/_layout.less @@ -91,7 +91,7 @@ footer.footer { // @gutter-width // 0 // '' | false | value -// Distanse between columns +// Distance between columns // // // Variables for layout columns @@ -361,7 +361,7 @@ footer.footer { // # Layout width // -// The .lib-layout-width() mixin is used to set default page width of the element the mixin is applyed to. It can be used to set width for the whole page wrapper or for the page elements individualy like header, footer, and so on. +// The .lib-layout-width() mixin is used to set default page width of the element the mixin is applied to. It can be used to set width for the whole page wrapper or for the page elements individualy like header, footer, and so on. // # Layout width variables // diff --git a/lib/web/css/docs/source/_typography.less b/lib/web/css/docs/source/_typography.less index 294f6446fdf13..b360c672a6d7c 100644 --- a/lib/web/css/docs/source/_typography.less +++ b/lib/web/css/docs/source/_typography.less @@ -3,7 +3,7 @@ // * See COPYING.txt for license details. // */ -// # Typogrphy +// # Typography // // Magento UI library provides mixins for typography styling. To configure typography, global variables from **_variables.less** file are used. // @@ -978,7 +978,7 @@ // # Text hide // -// The .lib-text-hide() mixin hides a text of the element the mixin is applyed to. +// The .lib-text-hide() mixin hides a text of the element the mixin is applied to. // // ```html // diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index 443ca40862ca4..8f20243383db8 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -575,7 +575,9 @@ define([ * @param {String} directive */ makeDirectiveUrl: function (directive) { - return this.config['directives_url'].replace(/directive.*/, 'directive/___directive/' + directive); + return this.config['directives_url'] + .replace(/directive/, 'directive/___directive/' + directive) + .replace(/\/$/, ''); }, /** @@ -606,11 +608,17 @@ define([ * @return {*} */ decodeDirectives: function (content) { - // escape special chars in directives url to use it in regular expression - var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), - reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)')); - - return content.gsub(reg, function (match) { //eslint-disable-line no-extra-bind + var directiveUrl = this.makeDirectiveUrl('%directive%').split('?')[0], // remove query string from directive + // escape special chars in directives url to use in regular expression + regexEscapedDirectiveUrl = directiveUrl.replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), + regexDirectiveUrl = regexEscapedDirectiveUrl + .replace( + '%directive%', + '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)(?:(?!").)*' + ) + '/?(\\\\?[^"]*)?', // allow optional query string + reg = new RegExp(regexDirectiveUrl); + + return content.gsub(reg, function (match) { return Base64.mageDecode(decodeURIComponent(match[1]).replace(/\/$/, '')).replace(/"/g, '"'); }); }, diff --git a/lib/web/mage/requirejs/resolver.js b/lib/web/mage/requirejs/resolver.js index f3d4b79216adb..5088206dd31d9 100644 --- a/lib/web/mage/requirejs/resolver.js +++ b/lib/web/mage/requirejs/resolver.js @@ -27,6 +27,16 @@ define([ return !!_.findWhere(callbacks, callback); } + /** + * Checks if provided module is rejected during load. + * + * @param {Object} module - Module to be checked. + * @return {Boolean} + */ + function isRejected(module) { + return registry[module.id] && registry[module.id].error; + } + /** * Checks if provided module has unresolved dependencies. * @@ -34,7 +44,11 @@ define([ * @returns {Boolean} */ function isPending(module) { - return !!module.depCount; + if (!module.depCount) { + return false; + } + + return module.depCount > _.filter(module.depMaps, isRejected).length; } /** diff --git a/pub/errors/processor.php b/pub/errors/processor.php index 896538853485e..7240707f642c2 100644 --- a/pub/errors/processor.php +++ b/pub/errors/processor.php @@ -463,7 +463,7 @@ protected function _setReportData($reportData) public function saveReport($reportData) { $this->reportData = $reportData; - $this->reportId = abs(intval(microtime(true) * rand(100, 1000))); + $this->reportId = abs(intval(microtime(true) * random_int(100, 1000))); $this->_reportFile = $this->_reportDir . '/' . $this->reportId; $this->_setReportData($reportData); diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 5dc1199c0e9da..2155f774b1e9a 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -38545,100 +38545,446 @@ adminUserList.add(vars.get("admin_user")); - - - - - false - sku - = - true - searchCriteria[filter_groups][0][filters][0][field] - - - false - bundle% - = - true - searchCriteria[filter_groups][0][filters][0][value] - - - false - like - = - true - searchCriteria[filter_groups][0][filters][0][condition_type] - - - false - 1 - = - true - searchCriteria[current_page] - - - false - 1 - = - true - searchCriteria[page_size] - - - - - - - - ${request_protocol} - - ${base_path}rest/default/V1/products - GET - true - false - true - false - false - - mpaf/tool/fragments/ce/api/get_bundle_product.jmx + + mpaf/tool/fragments/ce/api/create_bundle_product_with_extensible_data_objects.jmx + - - bundle_product_id - $.items[0].id - - - BODY - - - - bundle_product_sku - $.items[0].sku - - - BODY - - - - - ^\d+$ - - Assertion.response_data - false - 1 - variable - bundle_product_id - - - - - ^[a-zA-Z_0-9- ]+$ - - Assertion.response_data - false - 1 - variable - bundle_product_sku - - + + true + + + + false + { + "product": { + "sku": "bundle-apsku-test-${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "name": "Bundle Extensible_Product_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "visibility": "4", + "type_id": "bundle", + "price": "99", + "status": "1", + "attribute_set_id": "4", + "custom_attributes": [ + { + "attribute_code": "price_view", + "value": "0" + }, + { + "attribute_code": "price_type", + "value": "0" + }, + { + "attribute_code": "weight_type", + "value": "0" + }, + { + "attribute_code": "cost", + "value": "" + }, + { + "attribute_code": "description", + "value": "Description" + } + ], + "extension_attributes":{ + "stock_item":{ + "manage_stock": 1, + "is_in_stock": 1, + "qty":"100" + } + } , + "media_gallery_entries": + [{ + "id": null, + "label":"bundle_test_label_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "position":1, + "disabled":false, + "media_type":"image", + "types":["image"], + "content":{ + "base64_encoded_data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABgAGADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iioLy8t9Ps5bu7lWKCIZd26KKaTbshpX0RPRXN/8J/4V/6DVv8Ak3+FH/Cf+Ff+g1b/AJN/hXR9SxP/AD7l9zNPYVf5X9x0lFc3/wAJ/wCFf+g1b/k3+FH/AAn/AIV/6DVv+Tf4UfUsT/z7l9zD2FX+V/cdJRXN/wDCf+Ff+g1b/k3+FH/Cf+Ff+g1b/k3+FH1LE/8APuX3MPYVf5X9x0lFc3/wn/hX/oNW/wCTf4Uf8J/4V/6DVv8Ak3+FH1LE/wDPuX3MPYVf5X9x0lFVdP1G01WyS8sZ1nt3JCyL0ODg/qKtVzyi4u0lZmbTTswrm/H3/Iiav/1x/wDZhXSVzfj7/kRNX/64/wDswrowf+80/wDEvzNKH8WPqj5voorB1zS7OLT7m7SHE5YNu3HqWGeM471+kYutOhSdSEU7Jt3dtF20f6H1FacqcHJK9vO36M3qKzTa6foqPdxwlWxswrFi2T0AJ9aRdVmjkT7XYSW8TsFEm8MAT0yB0qfrcafu1tJeV2l2u7K3zsL2yjpPR+V3+NjTorPn1GVbt7a1s2uJIwDJ84ULnpyaik1SWTTrp47Z0uIQRJGzAFOPvZ70Sx1GLau9L9H03SdrNrsgdeCuu3k+hq0VR0ma4msImuIih2LtYvuLjA+b2zV6uijUVWmprqaQkpxUl1PoP4Xf8iBYf78v/oxq7GuO+F3/ACIFh/vy/wDoxq7GvzTMf98q/wCJ/mfLYn+NP1YVzfj7/kRNX/64/wDswrpK5vx9/wAiJq//AFx/9mFRg/8Aeaf+JfmTQ/ix9UfN9ZniD/kB3H/Af/QhWnTZI45kKSIroeqsMg1+l4mk61GdNfaTX3o+pqw54Sj3Rma/GXsI3BcLFMruU+8F5yR+dUZ4tOeNFOq3tx5jACNZg5J+mK6PrUMdrbxPvjgiR/7yoAa48TgPa1HNW1STvfp2s1+JjVw/PJy017mbe/YTqTB7iWzuQgPmhtocfjwajiupbjTtTieUXCxRsqTKMb8qePwrYlghnAE0UcgHQOoP86ckaRoERFVR/CowKbwU3UclJJO+19brqr203vvoHsJczd7J3/H8PmVNJnhm063WOVHZIkDhTkqcd/yNXajighg3eTFHHu67FAz+VSV2UIShTjGe67G9NOMUpbn0H8Lv+RAsP9+X/wBGNXY1x3wu/wCRAsP9+X/0Y1djX5tmP++Vf8T/ADPl8T/Gn6sK5vx9/wAiJq//AFx/9mFdJXN+Pv8AkRNX/wCuP/swqMH/ALzT/wAS/Mmh/Fj6o+b6KKK/Uj60KKKKACiiigAooooA+g/hd/yIFh/vy/8Aoxq7GuO+F3/IgWH+/L/6Mauxr8wzH/fKv+J/mfKYn+NP1YVzfj7/AJETV/8Arj/7MK6Sub8ff8iJq/8A1x/9mFRg/wDeaf8AiX5k0P4sfVHzfRRRX6kfWhRRRQAUUUUAFFFFAH0H8Lv+RAsP9+X/ANGNXY1x3wu/5ECw/wB+X/0Y1djX5hmP++Vf8T/M+UxP8afqwqC8s7fULOW0u4llglGHRujCp6K5E2ndGKdtUc3/AMIB4V/6Atv+bf40f8IB4V/6Atv+bf410lFdH13E/wDPyX3s09vV/mf3nN/8IB4V/wCgLb/m3+NH/CAeFf8AoC2/5t/jXSUUfXcT/wA/Jfew9vV/mf3nN/8ACAeFf+gLb/m3+NH/AAgHhX/oC2/5t/jXSUUfXcT/AM/Jfew9vV/mf3nN/wDCAeFf+gLb/m3+NH/CAeFf+gLb/m3+NdJRR9dxP/PyX3sPb1f5n95V0/TrTSrJLOxgWC3QkrGvQZOT+pq1RRXPKTk7yd2Zttu7P//Z", + "type": "image/jpeg", + "name": "bundle_test_image_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}.jpeg" + } + } + ] + } + } + = + + + + + + + + ${request_protocol} + + ${base_path}rest/default/V1/products + POST + true + false + true + false + false + + + + + bundle_product_id + $.id + + + BODY + + + + bundle_product_sku + $.sku + + + BODY + + + + + ^\d+$ + + Assertion.response_data + false + 1 + variable + bundle_product_id + + + + + ^[a-z0-9-]+$ + + Assertion.response_data + false + 1 + variable + bundle_product_sku + + + + + true + + + + false + { + "product": { + "sku": "apsku-test-${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "name": "Extensible_Product_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "visibility": "1", + "type_id": "simple", + "price": "3.62", + "status": "1", + "attribute_set_id": "4", + "custom_attributes": [ + { + "attribute_code": "cost", + "value": "" + }, + { + "attribute_code": "description", + "value": "Description" + } + ], + "extension_attributes":{ + "stock_item":{ + "manage_stock": 1, + "is_in_stock": 1, + "qty":"100" + } + } , + "media_gallery_entries": + [{ + "id": null, + "label":"test_label_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "position":1, + "disabled":false, + "media_type":"image", + "types":["image"], + "content":{ + "base64_encoded_data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABgAGADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iioLy8t9Ps5bu7lWKCIZd26KKaTbshpX0RPRXN/8J/4V/6DVv8Ak3+FH/Cf+Ff+g1b/AJN/hXR9SxP/AD7l9zNPYVf5X9x0lFc3/wAJ/wCFf+g1b/k3+FH/AAn/AIV/6DVv+Tf4UfUsT/z7l9zD2FX+V/cdJRXN/wDCf+Ff+g1b/k3+FH/Cf+Ff+g1b/k3+FH1LE/8APuX3MPYVf5X9x0lFc3/wn/hX/oNW/wCTf4Uf8J/4V/6DVv8Ak3+FH1LE/wDPuX3MPYVf5X9x0lFVdP1G01WyS8sZ1nt3JCyL0ODg/qKtVzyi4u0lZmbTTswrm/H3/Iiav/1x/wDZhXSVzfj7/kRNX/64/wDswrowf+80/wDEvzNKH8WPqj5voorB1zS7OLT7m7SHE5YNu3HqWGeM471+kYutOhSdSEU7Jt3dtF20f6H1FacqcHJK9vO36M3qKzTa6foqPdxwlWxswrFi2T0AJ9aRdVmjkT7XYSW8TsFEm8MAT0yB0qfrcafu1tJeV2l2u7K3zsL2yjpPR+V3+NjTorPn1GVbt7a1s2uJIwDJ84ULnpyaik1SWTTrp47Z0uIQRJGzAFOPvZ70Sx1GLau9L9H03SdrNrsgdeCuu3k+hq0VR0ma4msImuIih2LtYvuLjA+b2zV6uijUVWmprqaQkpxUl1PoP4Xf8iBYf78v/oxq7GuO+F3/ACIFh/vy/wDoxq7GvzTMf98q/wCJ/mfLYn+NP1YVzfj7/kRNX/64/wDswrpK5vx9/wAiJq//AFx/9mFRg/8Aeaf+JfmTQ/ix9UfN9ZniD/kB3H/Af/QhWnTZI45kKSIroeqsMg1+l4mk61GdNfaTX3o+pqw54Sj3Rma/GXsI3BcLFMruU+8F5yR+dUZ4tOeNFOq3tx5jACNZg5J+mK6PrUMdrbxPvjgiR/7yoAa48TgPa1HNW1STvfp2s1+JjVw/PJy017mbe/YTqTB7iWzuQgPmhtocfjwajiupbjTtTieUXCxRsqTKMb8qePwrYlghnAE0UcgHQOoP86ckaRoERFVR/CowKbwU3UclJJO+19brqr203vvoHsJczd7J3/H8PmVNJnhm063WOVHZIkDhTkqcd/yNXajighg3eTFHHu67FAz+VSV2UIShTjGe67G9NOMUpbn0H8Lv+RAsP9+X/wBGNXY1x3wu/wCRAsP9+X/0Y1djX5tmP++Vf8T/ADPl8T/Gn6sK5vx9/wAiJq//AFx/9mFdJXN+Pv8AkRNX/wCuP/swqMH/ALzT/wAS/Mmh/Fj6o+b6KKK/Uj60KKKKACiiigAooooA+g/hd/yIFh/vy/8Aoxq7GuO+F3/IgWH+/L/6Mauxr8wzH/fKv+J/mfKYn+NP1YVzfj7/AJETV/8Arj/7MK6Sub8ff8iJq/8A1x/9mFRg/wDeaf8AiX5k0P4sfVHzfRRRX6kfWhRRRQAUUUUAFFFFAH0H8Lv+RAsP9+X/ANGNXY1x3wu/5ECw/wB+X/0Y1djX5hmP++Vf8T/M+UxP8afqwqC8s7fULOW0u4llglGHRujCp6K5E2ndGKdtUc3/AMIB4V/6Atv+bf40f8IB4V/6Atv+bf410lFdH13E/wDPyX3s09vV/mf3nN/8IB4V/wCgLb/m3+NH/CAeFf8AoC2/5t/jXSUUfXcT/wA/Jfew9vV/mf3nN/8ACAeFf+gLb/m3+NH/AAgHhX/oC2/5t/jXSUUfXcT/AM/Jfew9vV/mf3nN/wDCAeFf+gLb/m3+NH/CAeFf+gLb/m3+NdJRR9dxP/PyX3sPb1f5n95V0/TrTSrJLOxgWC3QkrGvQZOT+pq1RRXPKTk7yd2Zttu7P//Z", + "type": "image/jpeg", + "name": "test_image_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}.jpeg" + } + } + ] + } + } + = + + + + + + + + ${request_protocol} + + ${base_path}rest/default/V1/products + POST + true + false + true + false + false + + + + + bundle_child_1_product_id + $.id + + + BODY + + + + bundle_child_1_product_sku + $.sku + + + BODY + + + + bundle_child_1_product_stock_item_id + $.extension_attributes.stock_item.item_id + + + BODY + + + + + ^\d+$ + + Assertion.response_data + false + 1 + variable + bundle_child_1_product_id + + + + + ^[A-Za-z0-9-]+$ + + Assertion.response_data + false + 1 + variable + bundle_child_1_product_sku + + + + + ^\d+$ + + Assertion.response_data + false + 1 + variable + bundle_child_1_product_stock_item_id + + + + + true + + + + false + { + "product": { + "sku": "apsku-test-${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "name": "Extensible_Product_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "visibility": "1", + "type_id": "simple", + "price": "3.62", + "status": "1", + "attribute_set_id": "4", + "custom_attributes": [ + { + "attribute_code": "cost", + "value": "" + }, + { + "attribute_code": "description", + "value": "Description" + } + ], + "extension_attributes":{ + "stock_item":{ + "manage_stock": 1, + "is_in_stock": 1, + "qty":"100" + } + } , + "media_gallery_entries": + [{ + "id": null, + "label":"test_label_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "position":1, + "disabled":false, + "media_type":"image", + "types":["image"], + "content":{ + "base64_encoded_data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABgAGADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iioLy8t9Ps5bu7lWKCIZd26KKaTbshpX0RPRXN/8J/4V/6DVv8Ak3+FH/Cf+Ff+g1b/AJN/hXR9SxP/AD7l9zNPYVf5X9x0lFc3/wAJ/wCFf+g1b/k3+FH/AAn/AIV/6DVv+Tf4UfUsT/z7l9zD2FX+V/cdJRXN/wDCf+Ff+g1b/k3+FH/Cf+Ff+g1b/k3+FH1LE/8APuX3MPYVf5X9x0lFc3/wn/hX/oNW/wCTf4Uf8J/4V/6DVv8Ak3+FH1LE/wDPuX3MPYVf5X9x0lFVdP1G01WyS8sZ1nt3JCyL0ODg/qKtVzyi4u0lZmbTTswrm/H3/Iiav/1x/wDZhXSVzfj7/kRNX/64/wDswrowf+80/wDEvzNKH8WPqj5voorB1zS7OLT7m7SHE5YNu3HqWGeM471+kYutOhSdSEU7Jt3dtF20f6H1FacqcHJK9vO36M3qKzTa6foqPdxwlWxswrFi2T0AJ9aRdVmjkT7XYSW8TsFEm8MAT0yB0qfrcafu1tJeV2l2u7K3zsL2yjpPR+V3+NjTorPn1GVbt7a1s2uJIwDJ84ULnpyaik1SWTTrp47Z0uIQRJGzAFOPvZ70Sx1GLau9L9H03SdrNrsgdeCuu3k+hq0VR0ma4msImuIih2LtYvuLjA+b2zV6uijUVWmprqaQkpxUl1PoP4Xf8iBYf78v/oxq7GuO+F3/ACIFh/vy/wDoxq7GvzTMf98q/wCJ/mfLYn+NP1YVzfj7/kRNX/64/wDswrpK5vx9/wAiJq//AFx/9mFRg/8Aeaf+JfmTQ/ix9UfN9ZniD/kB3H/Af/QhWnTZI45kKSIroeqsMg1+l4mk61GdNfaTX3o+pqw54Sj3Rma/GXsI3BcLFMruU+8F5yR+dUZ4tOeNFOq3tx5jACNZg5J+mK6PrUMdrbxPvjgiR/7yoAa48TgPa1HNW1STvfp2s1+JjVw/PJy017mbe/YTqTB7iWzuQgPmhtocfjwajiupbjTtTieUXCxRsqTKMb8qePwrYlghnAE0UcgHQOoP86ckaRoERFVR/CowKbwU3UclJJO+19brqr203vvoHsJczd7J3/H8PmVNJnhm063WOVHZIkDhTkqcd/yNXajighg3eTFHHu67FAz+VSV2UIShTjGe67G9NOMUpbn0H8Lv+RAsP9+X/wBGNXY1x3wu/wCRAsP9+X/0Y1djX5tmP++Vf8T/ADPl8T/Gn6sK5vx9/wAiJq//AFx/9mFdJXN+Pv8AkRNX/wCuP/swqMH/ALzT/wAS/Mmh/Fj6o+b6KKK/Uj60KKKKACiiigAooooA+g/hd/yIFh/vy/8Aoxq7GuO+F3/IgWH+/L/6Mauxr8wzH/fKv+J/mfKYn+NP1YVzfj7/AJETV/8Arj/7MK6Sub8ff8iJq/8A1x/9mFRg/wDeaf8AiX5k0P4sfVHzfRRRX6kfWhRRRQAUUUUAFFFFAH0H8Lv+RAsP9+X/ANGNXY1x3wu/5ECw/wB+X/0Y1djX5hmP++Vf8T/M+UxP8afqwqC8s7fULOW0u4llglGHRujCp6K5E2ndGKdtUc3/AMIB4V/6Atv+bf40f8IB4V/6Atv+bf410lFdH13E/wDPyX3s09vV/mf3nN/8IB4V/wCgLb/m3+NH/CAeFf8AoC2/5t/jXSUUfXcT/wA/Jfew9vV/mf3nN/8ACAeFf+gLb/m3+NH/AAgHhX/oC2/5t/jXSUUfXcT/AM/Jfew9vV/mf3nN/wDCAeFf+gLb/m3+NH/CAeFf+gLb/m3+NdJRR9dxP/PyX3sPb1f5n95V0/TrTSrJLOxgWC3QkrGvQZOT+pq1RRXPKTk7yd2Zttu7P//Z", + "type": "image/jpeg", + "name": "test_image_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}.jpeg" + } + } + ] + } + } + = + + + + + + + + ${request_protocol} + + ${base_path}rest/default/V1/products + POST + true + false + true + false + false + + + + + bundle_child_2_product_id + $.id + + + BODY + + + + bundle_child_2_product_sku + $.sku + + + BODY + + + + bundle_child_2_product_stock_item_id + $.extension_attributes.stock_item.item_id + + + BODY + + + + + ^\d+$ + + Assertion.response_data + false + 1 + variable + bundle_child_2_product_id + + + + + ^[A-Za-z0-9-]+$ + + Assertion.response_data + false + 1 + variable + bundle_child_2_product_sku + + + + + ^\d+$ + + Assertion.response_data + false + 1 + variable + bundle_child_2_product_stock_item_id + + + + + true + + + + false + { + "option": { + "title": "option-${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "required": 1, + "type": "select", + "position": 0, + "sku": "${bundle_product_sku}", + "product_links": [ + { + "sku": "${bundle_child_1_product_sku}", + "qty": 1, + "position": 0, + "is_default": 1, + "can_change_quantity": 0 + }, + { + "sku": "${bundle_child_2_product_sku}", + "qty": 1, + "position": 0, + "is_default": 0, + "can_change_quantity": 0 + } + ] + } + } + = + + + + + + + + ${request_protocol} + + ${base_path}rest/default/V1/bundle-products/options/add + POST + true + false + true + false + false + + + + + + ^\"\d+\"$ + + Assertion.response_data + false + 1 + + + + @@ -39511,7 +39857,7 @@ adminUserList.add(vars.get("admin_user")); false - {"query":"{\n products(\n filter: {\n price: {gt: \"10\"}\n or: {\n sku:{like:\"%Product%\"}\n name:{like:\"%Configurable Product%\"}\n }\n }\n pageSize: 200\n currentPage: 1\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(\n filter: {\n price: {gt: \"10\"}\n or: {\n sku:{like:\"%Product%\"}\n name:{like:\"%Configurable Product%\"}\n }\n }\n pageSize: 200\n currentPage: 1\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n description\n gift_message_available\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39568,7 +39914,7 @@ adminUserList.add(vars.get("admin_user")); false - {"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39644,7 +39990,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39713,7 +40059,7 @@ if (totalCount == null) { false - {"query":"{\n products(\n search: \"configurable\"\n filter: {price: {gteq: \"1\"} }\n ) {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(\n search: \"configurable\"\n filter: {price: {gteq: \"1\"} }\n ) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39773,7 +40119,7 @@ if (totalCount == null) { false - {"query":"{\n products(search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39833,7 +40179,7 @@ if (totalCount == null) { false - {"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} + {"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} = @@ -39887,13 +40233,13 @@ if (totalCount == null) { - + true false - {"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39962,7 +40308,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40049,7 +40395,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40116,7 +40462,7 @@ if (totalCount == null) { false - {"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40374,7 +40720,7 @@ if (totalCount == null) { false - {"query":"{\n categories(filter: {root_category_id: 4}) {\n category_tree {\n id\n level\n is_active\n description\n all_children\n path\n path_in_store\n product_count\n url_key\n url_path\n children {\n id\n is_active\n description\n available_sort_by\n default_sort_by\n image\n level\n children {\n id\n is_active\n filter_price_range\n description\n image\n meta_keywords\n level\n is_anchor\n children {\n level\n id\n children {\n id\n }\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} + {"query":"{\n category(id: 1) {\n name\n id\n level\n description\n path\n path_in_store\n url_key\n url_path\n children {\n id\n description\n default_sort_by\n children {\n id\n description\n level\n children {\n level\n id\n children {\n id\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} = @@ -40397,12 +40743,32 @@ if (totalCount == null) { - graphql_category_query_category_tree - $.data.categories.category_tree + graphql_category_query_name + $.data.category.name BODY + + + String name = vars.get("graphql_category_query_name"); +if (name == null) { + Failure = true; + FailureMessage = "Not Expected \"children\" to be null"; +} else { + if (!name.equals("Root Catalog")) { + Failure = true; + FailureMessage = "Expected \"name\" to equal \"Root Catalog\", Actual: " + name; + } else { + Failure = false; + } +} + + + + false + + diff --git a/setup/pub/magento/setup/select-version.js b/setup/pub/magento/setup/select-version.js index d9d4f7518ad1f..32210d29dcbfe 100644 --- a/setup/pub/magento/setup/select-version.js +++ b/setup/pub/magento/setup/select-version.js @@ -13,6 +13,7 @@ angular.module('select-version', ['ngStorage']) $scope.upgradeReadyForNext = false; $scope.upgradeProcessed = false; $scope.upgradeProcessError = false; + $scope.upgradeAlreadyLatestVersion = false; $scope.upgradeProcessErrorMessage = ''; $scope.componentsReadyForNext = true; $scope.componentsProcessed = false; @@ -39,6 +40,7 @@ angular.module('select-version', ['ngStorage']) if ($scope.upgradeProcessError) { $scope.upgradeProcessErrorMessage = "You're already using the latest version, there's nothing for us to do."; + $scope.upgradeAlreadyLatestVersion = true; } else { $scope.selectedOption = []; $scope.versions = []; diff --git a/setup/src/Magento/Setup/Model/Address/AddressDataGenerator.php b/setup/src/Magento/Setup/Model/Address/AddressDataGenerator.php index 2d450a4374c5e..1bbd152f2ba4e 100644 --- a/setup/src/Magento/Setup/Model/Address/AddressDataGenerator.php +++ b/setup/src/Magento/Setup/Model/Address/AddressDataGenerator.php @@ -18,7 +18,7 @@ class AddressDataGenerator public function generateAddress() { return [ - 'postcode' => mt_rand(10000, 99999) + 'postcode' => random_int(10000, 99999) ]; } } diff --git a/setup/src/Magento/Setup/Model/DataGenerator.php b/setup/src/Magento/Setup/Model/DataGenerator.php index 7a039388180a1..540433e5aa3ec 100644 --- a/setup/src/Magento/Setup/Model/DataGenerator.php +++ b/setup/src/Magento/Setup/Model/DataGenerator.php @@ -67,12 +67,12 @@ protected function readData() */ public function generate($minAmountOfWords, $maxAmountOfWords, $key = null) { - $numberOfWords = mt_rand($minAmountOfWords, $maxAmountOfWords); + $numberOfWords = random_int($minAmountOfWords, $maxAmountOfWords); $result = ''; if ($key === null || !array_key_exists($key, $this->generatedValues)) { for ($i = 0; $i < $numberOfWords; $i++) { - $result .= ' ' . $this->dictionaryData[mt_rand(0, count($this->dictionaryData) - 1)]; + $result .= ' ' . $this->dictionaryData[random_int(0, count($this->dictionaryData) - 1)]; } $result = trim($result); diff --git a/setup/src/Magento/Setup/Model/Description/DescriptionGenerator.php b/setup/src/Magento/Setup/Model/Description/DescriptionGenerator.php index 807e1fde7d90d..a790bfdbe608d 100644 --- a/setup/src/Magento/Setup/Model/Description/DescriptionGenerator.php +++ b/setup/src/Magento/Setup/Model/Description/DescriptionGenerator.php @@ -63,7 +63,7 @@ public function generate() */ private function generateRawDescription() { - $paragraphsCount = mt_rand( + $paragraphsCount = random_int( $this->descriptionConfig['paragraphs']['count-min'], $this->descriptionConfig['paragraphs']['count-max'] ); diff --git a/setup/src/Magento/Setup/Model/Description/DescriptionParagraphGenerator.php b/setup/src/Magento/Setup/Model/Description/DescriptionParagraphGenerator.php index 50544e6ea9726..57ece1f9558c3 100644 --- a/setup/src/Magento/Setup/Model/Description/DescriptionParagraphGenerator.php +++ b/setup/src/Magento/Setup/Model/Description/DescriptionParagraphGenerator.php @@ -39,7 +39,7 @@ public function __construct( */ public function generate() { - $sentencesCount = mt_rand( + $sentencesCount = random_int( $this->paragraphConfig['sentences']['count-min'], $this->paragraphConfig['sentences']['count-max'] ); diff --git a/setup/src/Magento/Setup/Model/Description/DescriptionSentenceGenerator.php b/setup/src/Magento/Setup/Model/Description/DescriptionSentenceGenerator.php index 299b4b50bed0f..10b07e7e1c7a2 100644 --- a/setup/src/Magento/Setup/Model/Description/DescriptionSentenceGenerator.php +++ b/setup/src/Magento/Setup/Model/Description/DescriptionSentenceGenerator.php @@ -39,7 +39,7 @@ public function __construct( */ public function generate() { - $sentenceWordsCount = mt_rand( + $sentenceWordsCount = random_int( $this->sentenceConfig['words']['count-min'], $this->sentenceConfig['words']['count-max'] ); diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php b/setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php index db208adc67de8..927759c4bfa7c 100644 --- a/setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php +++ b/setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php @@ -48,7 +48,7 @@ public function apply($text) return $this->wordWrapper->wrapWords( $text, - $this->randomWordSelector->getRandomWords($rawText, mt_rand(5, 8)), + $this->randomWordSelector->getRandomWords($rawText, random_int(5, 8)), '%s' ); } diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/Helper/RandomWordSelector.php b/setup/src/Magento/Setup/Model/Description/Mixin/Helper/RandomWordSelector.php index 0598db218728f..c7efcc7f12e0f 100644 --- a/setup/src/Magento/Setup/Model/Description/Mixin/Helper/RandomWordSelector.php +++ b/setup/src/Magento/Setup/Model/Description/Mixin/Helper/RandomWordSelector.php @@ -27,7 +27,7 @@ public function getRandomWords($source, $count) $randWords = []; $wordsSize = count($words); while ($count) { - $randWords[] = $words[mt_rand(0, $wordsSize - 1)]; + $randWords[] = $words[random_int(0, $wordsSize - 1)]; $count--; } diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/ItalicMixin.php b/setup/src/Magento/Setup/Model/Description/Mixin/ItalicMixin.php index 87e033be330cf..7621bccf08773 100644 --- a/setup/src/Magento/Setup/Model/Description/Mixin/ItalicMixin.php +++ b/setup/src/Magento/Setup/Model/Description/Mixin/ItalicMixin.php @@ -48,7 +48,7 @@ public function apply($text) return $this->wordWrapper->wrapWords( $text, - $this->randomWordSelector->getRandomWords($rawText, mt_rand(5, 8)), + $this->randomWordSelector->getRandomWords($rawText, random_int(5, 8)), '%s' ); } diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php b/setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php index ed5a836129460..fe53ebef535a8 100644 --- a/setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php +++ b/setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php @@ -48,7 +48,7 @@ public function apply($text) return $this->wordWrapper->wrapWords( $text, - $this->randomWordSelector->getRandomWords($rawText, mt_rand(5, 8)), + $this->randomWordSelector->getRandomWords($rawText, random_int(5, 8)), '%s' ); } diff --git a/setup/src/Magento/Setup/Model/Dictionary.php b/setup/src/Magento/Setup/Model/Dictionary.php index 630d35092d0fc..52f7cc1bb5148 100644 --- a/setup/src/Magento/Setup/Model/Dictionary.php +++ b/setup/src/Magento/Setup/Model/Dictionary.php @@ -40,7 +40,7 @@ public function getRandWord() $this->readDictionary(); } - $randIndex = mt_rand(0, count($this->dictionary) - 1); + $randIndex = random_int(0, count($this->dictionary) - 1); return trim($this->dictionary[$randIndex]); } diff --git a/setup/view/magento/setup/select-version.phtml b/setup/view/magento/setup/select-version.phtml index 4c61837d975a0..920ab86252514 100644 --- a/setup/view/magento/setup/select-version.phtml +++ b/setup/view/magento/setup/select-version.phtml @@ -38,7 +38,10 @@ Checking for a new version... - +
' + - '
' + - '' + - '
' + + '
' + + '' + + '
@_button-icon-font-position
before
.lib-link-as-button()
.lib-button()
.lib-layout-width()
.lib-text-hide()
diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index 443ca40862ca4..8f20243383db8 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -575,7 +575,9 @@ define([ * @param {String} directive */ makeDirectiveUrl: function (directive) { - return this.config['directives_url'].replace(/directive.*/, 'directive/___directive/' + directive); + return this.config['directives_url'] + .replace(/directive/, 'directive/___directive/' + directive) + .replace(/\/$/, ''); }, /** @@ -606,11 +608,17 @@ define([ * @return {*} */ decodeDirectives: function (content) { - // escape special chars in directives url to use it in regular expression - var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), - reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)')); - - return content.gsub(reg, function (match) { //eslint-disable-line no-extra-bind + var directiveUrl = this.makeDirectiveUrl('%directive%').split('?')[0], // remove query string from directive + // escape special chars in directives url to use in regular expression + regexEscapedDirectiveUrl = directiveUrl.replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), + regexDirectiveUrl = regexEscapedDirectiveUrl + .replace( + '%directive%', + '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)(?:(?!").)*' + ) + '/?(\\\\?[^"]*)?', // allow optional query string + reg = new RegExp(regexDirectiveUrl); + + return content.gsub(reg, function (match) { return Base64.mageDecode(decodeURIComponent(match[1]).replace(/\/$/, '')).replace(/"/g, '"'); }); }, diff --git a/lib/web/mage/requirejs/resolver.js b/lib/web/mage/requirejs/resolver.js index f3d4b79216adb..5088206dd31d9 100644 --- a/lib/web/mage/requirejs/resolver.js +++ b/lib/web/mage/requirejs/resolver.js @@ -27,6 +27,16 @@ define([ return !!_.findWhere(callbacks, callback); } + /** + * Checks if provided module is rejected during load. + * + * @param {Object} module - Module to be checked. + * @return {Boolean} + */ + function isRejected(module) { + return registry[module.id] && registry[module.id].error; + } + /** * Checks if provided module has unresolved dependencies. * @@ -34,7 +44,11 @@ define([ * @returns {Boolean} */ function isPending(module) { - return !!module.depCount; + if (!module.depCount) { + return false; + } + + return module.depCount > _.filter(module.depMaps, isRejected).length; } /** diff --git a/pub/errors/processor.php b/pub/errors/processor.php index 896538853485e..7240707f642c2 100644 --- a/pub/errors/processor.php +++ b/pub/errors/processor.php @@ -463,7 +463,7 @@ protected function _setReportData($reportData) public function saveReport($reportData) { $this->reportData = $reportData; - $this->reportId = abs(intval(microtime(true) * rand(100, 1000))); + $this->reportId = abs(intval(microtime(true) * random_int(100, 1000))); $this->_reportFile = $this->_reportDir . '/' . $this->reportId; $this->_setReportData($reportData); diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 5dc1199c0e9da..2155f774b1e9a 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -38545,100 +38545,446 @@ adminUserList.add(vars.get("admin_user")); - - - - - false - sku - = - true - searchCriteria[filter_groups][0][filters][0][field] - - - false - bundle% - = - true - searchCriteria[filter_groups][0][filters][0][value] - - - false - like - = - true - searchCriteria[filter_groups][0][filters][0][condition_type] - - - false - 1 - = - true - searchCriteria[current_page] - - - false - 1 - = - true - searchCriteria[page_size] - - - - - - - - ${request_protocol} - - ${base_path}rest/default/V1/products - GET - true - false - true - false - false - - mpaf/tool/fragments/ce/api/get_bundle_product.jmx + + mpaf/tool/fragments/ce/api/create_bundle_product_with_extensible_data_objects.jmx + - - bundle_product_id - $.items[0].id - - - BODY - - - - bundle_product_sku - $.items[0].sku - - - BODY - - - - - ^\d+$ - - Assertion.response_data - false - 1 - variable - bundle_product_id - - - - - ^[a-zA-Z_0-9- ]+$ - - Assertion.response_data - false - 1 - variable - bundle_product_sku - - + + true + + + + false + { + "product": { + "sku": "bundle-apsku-test-${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "name": "Bundle Extensible_Product_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "visibility": "4", + "type_id": "bundle", + "price": "99", + "status": "1", + "attribute_set_id": "4", + "custom_attributes": [ + { + "attribute_code": "price_view", + "value": "0" + }, + { + "attribute_code": "price_type", + "value": "0" + }, + { + "attribute_code": "weight_type", + "value": "0" + }, + { + "attribute_code": "cost", + "value": "" + }, + { + "attribute_code": "description", + "value": "Description" + } + ], + "extension_attributes":{ + "stock_item":{ + "manage_stock": 1, + "is_in_stock": 1, + "qty":"100" + } + } , + "media_gallery_entries": + [{ + "id": null, + "label":"bundle_test_label_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "position":1, + "disabled":false, + "media_type":"image", + "types":["image"], + "content":{ + "base64_encoded_data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABgAGADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iioLy8t9Ps5bu7lWKCIZd26KKaTbshpX0RPRXN/8J/4V/6DVv8Ak3+FH/Cf+Ff+g1b/AJN/hXR9SxP/AD7l9zNPYVf5X9x0lFc3/wAJ/wCFf+g1b/k3+FH/AAn/AIV/6DVv+Tf4UfUsT/z7l9zD2FX+V/cdJRXN/wDCf+Ff+g1b/k3+FH/Cf+Ff+g1b/k3+FH1LE/8APuX3MPYVf5X9x0lFc3/wn/hX/oNW/wCTf4Uf8J/4V/6DVv8Ak3+FH1LE/wDPuX3MPYVf5X9x0lFVdP1G01WyS8sZ1nt3JCyL0ODg/qKtVzyi4u0lZmbTTswrm/H3/Iiav/1x/wDZhXSVzfj7/kRNX/64/wDswrowf+80/wDEvzNKH8WPqj5voorB1zS7OLT7m7SHE5YNu3HqWGeM471+kYutOhSdSEU7Jt3dtF20f6H1FacqcHJK9vO36M3qKzTa6foqPdxwlWxswrFi2T0AJ9aRdVmjkT7XYSW8TsFEm8MAT0yB0qfrcafu1tJeV2l2u7K3zsL2yjpPR+V3+NjTorPn1GVbt7a1s2uJIwDJ84ULnpyaik1SWTTrp47Z0uIQRJGzAFOPvZ70Sx1GLau9L9H03SdrNrsgdeCuu3k+hq0VR0ma4msImuIih2LtYvuLjA+b2zV6uijUVWmprqaQkpxUl1PoP4Xf8iBYf78v/oxq7GuO+F3/ACIFh/vy/wDoxq7GvzTMf98q/wCJ/mfLYn+NP1YVzfj7/kRNX/64/wDswrpK5vx9/wAiJq//AFx/9mFRg/8Aeaf+JfmTQ/ix9UfN9ZniD/kB3H/Af/QhWnTZI45kKSIroeqsMg1+l4mk61GdNfaTX3o+pqw54Sj3Rma/GXsI3BcLFMruU+8F5yR+dUZ4tOeNFOq3tx5jACNZg5J+mK6PrUMdrbxPvjgiR/7yoAa48TgPa1HNW1STvfp2s1+JjVw/PJy017mbe/YTqTB7iWzuQgPmhtocfjwajiupbjTtTieUXCxRsqTKMb8qePwrYlghnAE0UcgHQOoP86ckaRoERFVR/CowKbwU3UclJJO+19brqr203vvoHsJczd7J3/H8PmVNJnhm063WOVHZIkDhTkqcd/yNXajighg3eTFHHu67FAz+VSV2UIShTjGe67G9NOMUpbn0H8Lv+RAsP9+X/wBGNXY1x3wu/wCRAsP9+X/0Y1djX5tmP++Vf8T/ADPl8T/Gn6sK5vx9/wAiJq//AFx/9mFdJXN+Pv8AkRNX/wCuP/swqMH/ALzT/wAS/Mmh/Fj6o+b6KKK/Uj60KKKKACiiigAooooA+g/hd/yIFh/vy/8Aoxq7GuO+F3/IgWH+/L/6Mauxr8wzH/fKv+J/mfKYn+NP1YVzfj7/AJETV/8Arj/7MK6Sub8ff8iJq/8A1x/9mFRg/wDeaf8AiX5k0P4sfVHzfRRRX6kfWhRRRQAUUUUAFFFFAH0H8Lv+RAsP9+X/ANGNXY1x3wu/5ECw/wB+X/0Y1djX5hmP++Vf8T/M+UxP8afqwqC8s7fULOW0u4llglGHRujCp6K5E2ndGKdtUc3/AMIB4V/6Atv+bf40f8IB4V/6Atv+bf410lFdH13E/wDPyX3s09vV/mf3nN/8IB4V/wCgLb/m3+NH/CAeFf8AoC2/5t/jXSUUfXcT/wA/Jfew9vV/mf3nN/8ACAeFf+gLb/m3+NH/AAgHhX/oC2/5t/jXSUUfXcT/AM/Jfew9vV/mf3nN/wDCAeFf+gLb/m3+NH/CAeFf+gLb/m3+NdJRR9dxP/PyX3sPb1f5n95V0/TrTSrJLOxgWC3QkrGvQZOT+pq1RRXPKTk7yd2Zttu7P//Z", + "type": "image/jpeg", + "name": "bundle_test_image_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}.jpeg" + } + } + ] + } + } + = + + + + + + + + ${request_protocol} + + ${base_path}rest/default/V1/products + POST + true + false + true + false + false + + + + + bundle_product_id + $.id + + + BODY + + + + bundle_product_sku + $.sku + + + BODY + + + + + ^\d+$ + + Assertion.response_data + false + 1 + variable + bundle_product_id + + + + + ^[a-z0-9-]+$ + + Assertion.response_data + false + 1 + variable + bundle_product_sku + + + + + true + + + + false + { + "product": { + "sku": "apsku-test-${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "name": "Extensible_Product_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "visibility": "1", + "type_id": "simple", + "price": "3.62", + "status": "1", + "attribute_set_id": "4", + "custom_attributes": [ + { + "attribute_code": "cost", + "value": "" + }, + { + "attribute_code": "description", + "value": "Description" + } + ], + "extension_attributes":{ + "stock_item":{ + "manage_stock": 1, + "is_in_stock": 1, + "qty":"100" + } + } , + "media_gallery_entries": + [{ + "id": null, + "label":"test_label_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "position":1, + "disabled":false, + "media_type":"image", + "types":["image"], + "content":{ + "base64_encoded_data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABgAGADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iioLy8t9Ps5bu7lWKCIZd26KKaTbshpX0RPRXN/8J/4V/6DVv8Ak3+FH/Cf+Ff+g1b/AJN/hXR9SxP/AD7l9zNPYVf5X9x0lFc3/wAJ/wCFf+g1b/k3+FH/AAn/AIV/6DVv+Tf4UfUsT/z7l9zD2FX+V/cdJRXN/wDCf+Ff+g1b/k3+FH/Cf+Ff+g1b/k3+FH1LE/8APuX3MPYVf5X9x0lFc3/wn/hX/oNW/wCTf4Uf8J/4V/6DVv8Ak3+FH1LE/wDPuX3MPYVf5X9x0lFVdP1G01WyS8sZ1nt3JCyL0ODg/qKtVzyi4u0lZmbTTswrm/H3/Iiav/1x/wDZhXSVzfj7/kRNX/64/wDswrowf+80/wDEvzNKH8WPqj5voorB1zS7OLT7m7SHE5YNu3HqWGeM471+kYutOhSdSEU7Jt3dtF20f6H1FacqcHJK9vO36M3qKzTa6foqPdxwlWxswrFi2T0AJ9aRdVmjkT7XYSW8TsFEm8MAT0yB0qfrcafu1tJeV2l2u7K3zsL2yjpPR+V3+NjTorPn1GVbt7a1s2uJIwDJ84ULnpyaik1SWTTrp47Z0uIQRJGzAFOPvZ70Sx1GLau9L9H03SdrNrsgdeCuu3k+hq0VR0ma4msImuIih2LtYvuLjA+b2zV6uijUVWmprqaQkpxUl1PoP4Xf8iBYf78v/oxq7GuO+F3/ACIFh/vy/wDoxq7GvzTMf98q/wCJ/mfLYn+NP1YVzfj7/kRNX/64/wDswrpK5vx9/wAiJq//AFx/9mFRg/8Aeaf+JfmTQ/ix9UfN9ZniD/kB3H/Af/QhWnTZI45kKSIroeqsMg1+l4mk61GdNfaTX3o+pqw54Sj3Rma/GXsI3BcLFMruU+8F5yR+dUZ4tOeNFOq3tx5jACNZg5J+mK6PrUMdrbxPvjgiR/7yoAa48TgPa1HNW1STvfp2s1+JjVw/PJy017mbe/YTqTB7iWzuQgPmhtocfjwajiupbjTtTieUXCxRsqTKMb8qePwrYlghnAE0UcgHQOoP86ckaRoERFVR/CowKbwU3UclJJO+19brqr203vvoHsJczd7J3/H8PmVNJnhm063WOVHZIkDhTkqcd/yNXajighg3eTFHHu67FAz+VSV2UIShTjGe67G9NOMUpbn0H8Lv+RAsP9+X/wBGNXY1x3wu/wCRAsP9+X/0Y1djX5tmP++Vf8T/ADPl8T/Gn6sK5vx9/wAiJq//AFx/9mFdJXN+Pv8AkRNX/wCuP/swqMH/ALzT/wAS/Mmh/Fj6o+b6KKK/Uj60KKKKACiiigAooooA+g/hd/yIFh/vy/8Aoxq7GuO+F3/IgWH+/L/6Mauxr8wzH/fKv+J/mfKYn+NP1YVzfj7/AJETV/8Arj/7MK6Sub8ff8iJq/8A1x/9mFRg/wDeaf8AiX5k0P4sfVHzfRRRX6kfWhRRRQAUUUUAFFFFAH0H8Lv+RAsP9+X/ANGNXY1x3wu/5ECw/wB+X/0Y1djX5hmP++Vf8T/M+UxP8afqwqC8s7fULOW0u4llglGHRujCp6K5E2ndGKdtUc3/AMIB4V/6Atv+bf40f8IB4V/6Atv+bf410lFdH13E/wDPyX3s09vV/mf3nN/8IB4V/wCgLb/m3+NH/CAeFf8AoC2/5t/jXSUUfXcT/wA/Jfew9vV/mf3nN/8ACAeFf+gLb/m3+NH/AAgHhX/oC2/5t/jXSUUfXcT/AM/Jfew9vV/mf3nN/wDCAeFf+gLb/m3+NH/CAeFf+gLb/m3+NdJRR9dxP/PyX3sPb1f5n95V0/TrTSrJLOxgWC3QkrGvQZOT+pq1RRXPKTk7yd2Zttu7P//Z", + "type": "image/jpeg", + "name": "test_image_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}.jpeg" + } + } + ] + } + } + = + + + + + + + + ${request_protocol} + + ${base_path}rest/default/V1/products + POST + true + false + true + false + false + + + + + bundle_child_1_product_id + $.id + + + BODY + + + + bundle_child_1_product_sku + $.sku + + + BODY + + + + bundle_child_1_product_stock_item_id + $.extension_attributes.stock_item.item_id + + + BODY + + + + + ^\d+$ + + Assertion.response_data + false + 1 + variable + bundle_child_1_product_id + + + + + ^[A-Za-z0-9-]+$ + + Assertion.response_data + false + 1 + variable + bundle_child_1_product_sku + + + + + ^\d+$ + + Assertion.response_data + false + 1 + variable + bundle_child_1_product_stock_item_id + + + + + true + + + + false + { + "product": { + "sku": "apsku-test-${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "name": "Extensible_Product_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "visibility": "1", + "type_id": "simple", + "price": "3.62", + "status": "1", + "attribute_set_id": "4", + "custom_attributes": [ + { + "attribute_code": "cost", + "value": "" + }, + { + "attribute_code": "description", + "value": "Description" + } + ], + "extension_attributes":{ + "stock_item":{ + "manage_stock": 1, + "is_in_stock": 1, + "qty":"100" + } + } , + "media_gallery_entries": + [{ + "id": null, + "label":"test_label_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "position":1, + "disabled":false, + "media_type":"image", + "types":["image"], + "content":{ + "base64_encoded_data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABgAGADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iioLy8t9Ps5bu7lWKCIZd26KKaTbshpX0RPRXN/8J/4V/6DVv8Ak3+FH/Cf+Ff+g1b/AJN/hXR9SxP/AD7l9zNPYVf5X9x0lFc3/wAJ/wCFf+g1b/k3+FH/AAn/AIV/6DVv+Tf4UfUsT/z7l9zD2FX+V/cdJRXN/wDCf+Ff+g1b/k3+FH/Cf+Ff+g1b/k3+FH1LE/8APuX3MPYVf5X9x0lFc3/wn/hX/oNW/wCTf4Uf8J/4V/6DVv8Ak3+FH1LE/wDPuX3MPYVf5X9x0lFVdP1G01WyS8sZ1nt3JCyL0ODg/qKtVzyi4u0lZmbTTswrm/H3/Iiav/1x/wDZhXSVzfj7/kRNX/64/wDswrowf+80/wDEvzNKH8WPqj5voorB1zS7OLT7m7SHE5YNu3HqWGeM471+kYutOhSdSEU7Jt3dtF20f6H1FacqcHJK9vO36M3qKzTa6foqPdxwlWxswrFi2T0AJ9aRdVmjkT7XYSW8TsFEm8MAT0yB0qfrcafu1tJeV2l2u7K3zsL2yjpPR+V3+NjTorPn1GVbt7a1s2uJIwDJ84ULnpyaik1SWTTrp47Z0uIQRJGzAFOPvZ70Sx1GLau9L9H03SdrNrsgdeCuu3k+hq0VR0ma4msImuIih2LtYvuLjA+b2zV6uijUVWmprqaQkpxUl1PoP4Xf8iBYf78v/oxq7GuO+F3/ACIFh/vy/wDoxq7GvzTMf98q/wCJ/mfLYn+NP1YVzfj7/kRNX/64/wDswrpK5vx9/wAiJq//AFx/9mFRg/8Aeaf+JfmTQ/ix9UfN9ZniD/kB3H/Af/QhWnTZI45kKSIroeqsMg1+l4mk61GdNfaTX3o+pqw54Sj3Rma/GXsI3BcLFMruU+8F5yR+dUZ4tOeNFOq3tx5jACNZg5J+mK6PrUMdrbxPvjgiR/7yoAa48TgPa1HNW1STvfp2s1+JjVw/PJy017mbe/YTqTB7iWzuQgPmhtocfjwajiupbjTtTieUXCxRsqTKMb8qePwrYlghnAE0UcgHQOoP86ckaRoERFVR/CowKbwU3UclJJO+19brqr203vvoHsJczd7J3/H8PmVNJnhm063WOVHZIkDhTkqcd/yNXajighg3eTFHHu67FAz+VSV2UIShTjGe67G9NOMUpbn0H8Lv+RAsP9+X/wBGNXY1x3wu/wCRAsP9+X/0Y1djX5tmP++Vf8T/ADPl8T/Gn6sK5vx9/wAiJq//AFx/9mFdJXN+Pv8AkRNX/wCuP/swqMH/ALzT/wAS/Mmh/Fj6o+b6KKK/Uj60KKKKACiiigAooooA+g/hd/yIFh/vy/8Aoxq7GuO+F3/IgWH+/L/6Mauxr8wzH/fKv+J/mfKYn+NP1YVzfj7/AJETV/8Arj/7MK6Sub8ff8iJq/8A1x/9mFRg/wDeaf8AiX5k0P4sfVHzfRRRX6kfWhRRRQAUUUUAFFFFAH0H8Lv+RAsP9+X/ANGNXY1x3wu/5ECw/wB+X/0Y1djX5hmP++Vf8T/M+UxP8afqwqC8s7fULOW0u4llglGHRujCp6K5E2ndGKdtUc3/AMIB4V/6Atv+bf40f8IB4V/6Atv+bf410lFdH13E/wDPyX3s09vV/mf3nN/8IB4V/wCgLb/m3+NH/CAeFf8AoC2/5t/jXSUUfXcT/wA/Jfew9vV/mf3nN/8ACAeFf+gLb/m3+NH/AAgHhX/oC2/5t/jXSUUfXcT/AM/Jfew9vV/mf3nN/wDCAeFf+gLb/m3+NH/CAeFf+gLb/m3+NdJRR9dxP/PyX3sPb1f5n95V0/TrTSrJLOxgWC3QkrGvQZOT+pq1RRXPKTk7yd2Zttu7P//Z", + "type": "image/jpeg", + "name": "test_image_${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}.jpeg" + } + } + ] + } + } + = + + + + + + + + ${request_protocol} + + ${base_path}rest/default/V1/products + POST + true + false + true + false + false + + + + + bundle_child_2_product_id + $.id + + + BODY + + + + bundle_child_2_product_sku + $.sku + + + BODY + + + + bundle_child_2_product_stock_item_id + $.extension_attributes.stock_item.item_id + + + BODY + + + + + ^\d+$ + + Assertion.response_data + false + 1 + variable + bundle_child_2_product_id + + + + + ^[A-Za-z0-9-]+$ + + Assertion.response_data + false + 1 + variable + bundle_child_2_product_sku + + + + + ^\d+$ + + Assertion.response_data + false + 1 + variable + bundle_child_2_product_stock_item_id + + + + + true + + + + false + { + "option": { + "title": "option-${__time(YMDHMS)}-${__threadNum}-${__Random(1,1000000)}", + "required": 1, + "type": "select", + "position": 0, + "sku": "${bundle_product_sku}", + "product_links": [ + { + "sku": "${bundle_child_1_product_sku}", + "qty": 1, + "position": 0, + "is_default": 1, + "can_change_quantity": 0 + }, + { + "sku": "${bundle_child_2_product_sku}", + "qty": 1, + "position": 0, + "is_default": 0, + "can_change_quantity": 0 + } + ] + } + } + = + + + + + + + + ${request_protocol} + + ${base_path}rest/default/V1/bundle-products/options/add + POST + true + false + true + false + false + + + + + + ^\"\d+\"$ + + Assertion.response_data + false + 1 + + + + @@ -39511,7 +39857,7 @@ adminUserList.add(vars.get("admin_user")); false - {"query":"{\n products(\n filter: {\n price: {gt: \"10\"}\n or: {\n sku:{like:\"%Product%\"}\n name:{like:\"%Configurable Product%\"}\n }\n }\n pageSize: 200\n currentPage: 1\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(\n filter: {\n price: {gt: \"10\"}\n or: {\n sku:{like:\"%Product%\"}\n name:{like:\"%Configurable Product%\"}\n }\n }\n pageSize: 200\n currentPage: 1\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n description\n gift_message_available\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39568,7 +39914,7 @@ adminUserList.add(vars.get("admin_user")); false - {"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39644,7 +39990,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39713,7 +40059,7 @@ if (totalCount == null) { false - {"query":"{\n products(\n search: \"configurable\"\n filter: {price: {gteq: \"1\"} }\n ) {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(\n search: \"configurable\"\n filter: {price: {gteq: \"1\"} }\n ) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39773,7 +40119,7 @@ if (totalCount == null) { false - {"query":"{\n products(search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39833,7 +40179,7 @@ if (totalCount == null) { false - {"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} + {"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} = @@ -39887,13 +40233,13 @@ if (totalCount == null) { - + true false - {"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39962,7 +40308,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40049,7 +40395,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40116,7 +40462,7 @@ if (totalCount == null) { false - {"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40374,7 +40720,7 @@ if (totalCount == null) { false - {"query":"{\n categories(filter: {root_category_id: 4}) {\n category_tree {\n id\n level\n is_active\n description\n all_children\n path\n path_in_store\n product_count\n url_key\n url_path\n children {\n id\n is_active\n description\n available_sort_by\n default_sort_by\n image\n level\n children {\n id\n is_active\n filter_price_range\n description\n image\n meta_keywords\n level\n is_anchor\n children {\n level\n id\n children {\n id\n }\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} + {"query":"{\n category(id: 1) {\n name\n id\n level\n description\n path\n path_in_store\n url_key\n url_path\n children {\n id\n description\n default_sort_by\n children {\n id\n description\n level\n children {\n level\n id\n children {\n id\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} = @@ -40397,12 +40743,32 @@ if (totalCount == null) { - graphql_category_query_category_tree - $.data.categories.category_tree + graphql_category_query_name + $.data.category.name BODY + + + String name = vars.get("graphql_category_query_name"); +if (name == null) { + Failure = true; + FailureMessage = "Not Expected \"children\" to be null"; +} else { + if (!name.equals("Root Catalog")) { + Failure = true; + FailureMessage = "Expected \"name\" to equal \"Root Catalog\", Actual: " + name; + } else { + Failure = false; + } +} + + + + false + + diff --git a/setup/pub/magento/setup/select-version.js b/setup/pub/magento/setup/select-version.js index d9d4f7518ad1f..32210d29dcbfe 100644 --- a/setup/pub/magento/setup/select-version.js +++ b/setup/pub/magento/setup/select-version.js @@ -13,6 +13,7 @@ angular.module('select-version', ['ngStorage']) $scope.upgradeReadyForNext = false; $scope.upgradeProcessed = false; $scope.upgradeProcessError = false; + $scope.upgradeAlreadyLatestVersion = false; $scope.upgradeProcessErrorMessage = ''; $scope.componentsReadyForNext = true; $scope.componentsProcessed = false; @@ -39,6 +40,7 @@ angular.module('select-version', ['ngStorage']) if ($scope.upgradeProcessError) { $scope.upgradeProcessErrorMessage = "You're already using the latest version, there's nothing for us to do."; + $scope.upgradeAlreadyLatestVersion = true; } else { $scope.selectedOption = []; $scope.versions = []; diff --git a/setup/src/Magento/Setup/Model/Address/AddressDataGenerator.php b/setup/src/Magento/Setup/Model/Address/AddressDataGenerator.php index 2d450a4374c5e..1bbd152f2ba4e 100644 --- a/setup/src/Magento/Setup/Model/Address/AddressDataGenerator.php +++ b/setup/src/Magento/Setup/Model/Address/AddressDataGenerator.php @@ -18,7 +18,7 @@ class AddressDataGenerator public function generateAddress() { return [ - 'postcode' => mt_rand(10000, 99999) + 'postcode' => random_int(10000, 99999) ]; } } diff --git a/setup/src/Magento/Setup/Model/DataGenerator.php b/setup/src/Magento/Setup/Model/DataGenerator.php index 7a039388180a1..540433e5aa3ec 100644 --- a/setup/src/Magento/Setup/Model/DataGenerator.php +++ b/setup/src/Magento/Setup/Model/DataGenerator.php @@ -67,12 +67,12 @@ protected function readData() */ public function generate($minAmountOfWords, $maxAmountOfWords, $key = null) { - $numberOfWords = mt_rand($minAmountOfWords, $maxAmountOfWords); + $numberOfWords = random_int($minAmountOfWords, $maxAmountOfWords); $result = ''; if ($key === null || !array_key_exists($key, $this->generatedValues)) { for ($i = 0; $i < $numberOfWords; $i++) { - $result .= ' ' . $this->dictionaryData[mt_rand(0, count($this->dictionaryData) - 1)]; + $result .= ' ' . $this->dictionaryData[random_int(0, count($this->dictionaryData) - 1)]; } $result = trim($result); diff --git a/setup/src/Magento/Setup/Model/Description/DescriptionGenerator.php b/setup/src/Magento/Setup/Model/Description/DescriptionGenerator.php index 807e1fde7d90d..a790bfdbe608d 100644 --- a/setup/src/Magento/Setup/Model/Description/DescriptionGenerator.php +++ b/setup/src/Magento/Setup/Model/Description/DescriptionGenerator.php @@ -63,7 +63,7 @@ public function generate() */ private function generateRawDescription() { - $paragraphsCount = mt_rand( + $paragraphsCount = random_int( $this->descriptionConfig['paragraphs']['count-min'], $this->descriptionConfig['paragraphs']['count-max'] ); diff --git a/setup/src/Magento/Setup/Model/Description/DescriptionParagraphGenerator.php b/setup/src/Magento/Setup/Model/Description/DescriptionParagraphGenerator.php index 50544e6ea9726..57ece1f9558c3 100644 --- a/setup/src/Magento/Setup/Model/Description/DescriptionParagraphGenerator.php +++ b/setup/src/Magento/Setup/Model/Description/DescriptionParagraphGenerator.php @@ -39,7 +39,7 @@ public function __construct( */ public function generate() { - $sentencesCount = mt_rand( + $sentencesCount = random_int( $this->paragraphConfig['sentences']['count-min'], $this->paragraphConfig['sentences']['count-max'] ); diff --git a/setup/src/Magento/Setup/Model/Description/DescriptionSentenceGenerator.php b/setup/src/Magento/Setup/Model/Description/DescriptionSentenceGenerator.php index 299b4b50bed0f..10b07e7e1c7a2 100644 --- a/setup/src/Magento/Setup/Model/Description/DescriptionSentenceGenerator.php +++ b/setup/src/Magento/Setup/Model/Description/DescriptionSentenceGenerator.php @@ -39,7 +39,7 @@ public function __construct( */ public function generate() { - $sentenceWordsCount = mt_rand( + $sentenceWordsCount = random_int( $this->sentenceConfig['words']['count-min'], $this->sentenceConfig['words']['count-max'] ); diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php b/setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php index db208adc67de8..927759c4bfa7c 100644 --- a/setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php +++ b/setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php @@ -48,7 +48,7 @@ public function apply($text) return $this->wordWrapper->wrapWords( $text, - $this->randomWordSelector->getRandomWords($rawText, mt_rand(5, 8)), + $this->randomWordSelector->getRandomWords($rawText, random_int(5, 8)), '%s' ); } diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/Helper/RandomWordSelector.php b/setup/src/Magento/Setup/Model/Description/Mixin/Helper/RandomWordSelector.php index 0598db218728f..c7efcc7f12e0f 100644 --- a/setup/src/Magento/Setup/Model/Description/Mixin/Helper/RandomWordSelector.php +++ b/setup/src/Magento/Setup/Model/Description/Mixin/Helper/RandomWordSelector.php @@ -27,7 +27,7 @@ public function getRandomWords($source, $count) $randWords = []; $wordsSize = count($words); while ($count) { - $randWords[] = $words[mt_rand(0, $wordsSize - 1)]; + $randWords[] = $words[random_int(0, $wordsSize - 1)]; $count--; } diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/ItalicMixin.php b/setup/src/Magento/Setup/Model/Description/Mixin/ItalicMixin.php index 87e033be330cf..7621bccf08773 100644 --- a/setup/src/Magento/Setup/Model/Description/Mixin/ItalicMixin.php +++ b/setup/src/Magento/Setup/Model/Description/Mixin/ItalicMixin.php @@ -48,7 +48,7 @@ public function apply($text) return $this->wordWrapper->wrapWords( $text, - $this->randomWordSelector->getRandomWords($rawText, mt_rand(5, 8)), + $this->randomWordSelector->getRandomWords($rawText, random_int(5, 8)), '%s' ); } diff --git a/setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php b/setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php index ed5a836129460..fe53ebef535a8 100644 --- a/setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php +++ b/setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php @@ -48,7 +48,7 @@ public function apply($text) return $this->wordWrapper->wrapWords( $text, - $this->randomWordSelector->getRandomWords($rawText, mt_rand(5, 8)), + $this->randomWordSelector->getRandomWords($rawText, random_int(5, 8)), '%s' ); } diff --git a/setup/src/Magento/Setup/Model/Dictionary.php b/setup/src/Magento/Setup/Model/Dictionary.php index 630d35092d0fc..52f7cc1bb5148 100644 --- a/setup/src/Magento/Setup/Model/Dictionary.php +++ b/setup/src/Magento/Setup/Model/Dictionary.php @@ -40,7 +40,7 @@ public function getRandWord() $this->readDictionary(); } - $randIndex = mt_rand(0, count($this->dictionary) - 1); + $randIndex = random_int(0, count($this->dictionary) - 1); return trim($this->dictionary[$randIndex]); } diff --git a/setup/view/magento/setup/select-version.phtml b/setup/view/magento/setup/select-version.phtml index 4c61837d975a0..920ab86252514 100644 --- a/setup/view/magento/setup/select-version.phtml +++ b/setup/view/magento/setup/select-version.phtml @@ -38,7 +38,10 @@ Checking for a new version... - +