From be02c1e1cd1b1547b3d087be9a96ea7a12f2b46c Mon Sep 17 00:00:00 2001 From: Seth Daugherty Date: Fri, 4 Jan 2019 18:59:55 -0500 Subject: [PATCH 001/108] 20078 Add support for validation message callback - Adds a type check for message just before attempting to process validation params on it - Resolves the callback if it was one using the validation rule for the 'this' context --- .../Magento/Ui/view/base/web/js/lib/validation/validator.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js index be8fd2ce9fcef..577b5eabb0350 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js @@ -48,6 +48,10 @@ define([ params : [params]; + if (typeof message === "function") { + message = message.call(rule); + } + message = params.reduce(function (msg, param, idx) { return msg.replace(new RegExp('\\{' + idx + '\\}', 'g'), param); }, message); From dc8d2d5a316ec9013a97d5850f78d39446351131 Mon Sep 17 00:00:00 2001 From: Seth Daugherty Date: Mon, 7 Jan 2019 11:16:58 -0500 Subject: [PATCH 002/108] Update quotes - Change double quotes to single quotes --- .../Magento/Ui/view/base/web/js/lib/validation/validator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js index 577b5eabb0350..8ebbf88775b86 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js @@ -48,7 +48,7 @@ define([ params : [params]; - if (typeof message === "function") { + if (typeof message === 'function') { message = message.call(rule); } From b1b6c3c9e9878c96e0b021c9489b6fdd490ea17f Mon Sep 17 00:00:00 2001 From: milindsingh Date: Tue, 8 Jan 2019 22:29:19 +0530 Subject: [PATCH 003/108] Issue fixed #20128 : Date range returns same start and end date --- .../Magento/Reports/Model/ResourceModel/Order/Collection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php index 82ebc74a0468e..bc0c8d89c727e 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php @@ -446,7 +446,7 @@ public function getDateRange($range, $customStart, $customEnd, $returnObjects = break; case 'custom': - $dateStart = $customStart ? $customStart : $dateEnd; + $dateStart = $customStart ? $customStart : $dateStart; $dateEnd = $customEnd ? $customEnd : $dateEnd; break; From 021a953482e06a622d1508fe35aa4e744d636493 Mon Sep 17 00:00:00 2001 From: Vitaliy Honcharenko Date: Tue, 18 Dec 2018 18:20:43 +0200 Subject: [PATCH 004/108] MAGETWO-95294: Mysql search slow on the catalog page --- .../Catalog/Block/Product/ListProduct.php | 4 +- .../Magento/CatalogSearch/Model/Advanced.php | 50 ++++- .../ProductCollectionPrepareStrategy.php | 61 +++++ ...ductCollectionPrepareStrategyInterface.php | 22 ++ ...oductCollectionPrepareStrategyProvider.php | 47 ++++ .../ResourceModel/Advanced/Collection.php | 97 ++++++-- .../ResourceModel/Fulltext/Collection.php | 164 ++++++++++---- .../Collection/SearchCriteriaResolver.php | 51 +++++ .../SearchCriteriaResolverInterface.php | 20 ++ .../Collection/SearchResultApplier.php | 65 ++++++ .../SearchResultApplierInterface.php | 18 ++ .../Model/Search/ItemCollectionProvider.php | 50 +++++ .../ItemCollectionProviderInterface.php | 21 ++ .../Test/Unit/Model/AdvancedTest.php | 1 + .../ResourceModel/Advanced/CollectionTest.php | 46 +++- .../ResourceModel/Fulltext/CollectionTest.php | 56 ++--- app/code/Magento/CatalogSearch/etc/di.xml | 27 ++- .../frontend/templates/advanced/result.phtml | 3 +- .../FieldProvider/FieldIndex/Converter.php | 8 +- .../Elasticsearch5/SearchAdapter/Adapter.php | 1 + .../SearchAdapter/Query/Builder.php | 15 +- .../FieldMapper/Product/AttributeAdapter.php | 10 + .../FieldProvider/FieldIndex/Converter.php | 8 +- .../FieldIndex/ConverterInterface.php | 1 + .../FieldName/Resolver/DefaultResolver.php | 21 +- .../FieldName/Resolver/SpecialAttribute.php | 4 +- .../Product/FieldProvider/StaticField.php | 42 +++- .../ProductCollectionPrepareStrategy.php | 41 ++++ .../Layer/Search/ItemCollectionProvider.php | 51 +++++ .../ResourceModel/Advanced/Collection.php | 51 +++++ .../ResourceModel/Fulltext/Collection.php | 54 +++++ .../Collection/SearchCriteriaResolver.php | 87 +++++++ .../Collection/SearchResultApplier.php | 60 +++++ .../Elasticsearch/SearchAdapter/Adapter.php | 1 + .../SearchAdapter/Query/Builder.php | 4 +- .../SearchAdapter/Query/Builder/Sort.php | 104 +++++++++ .../SearchAdapter/ResponseFactory.php | 1 + .../Resolver/DefaultResolverTest.php | 21 +- .../Product/FieldProvider/StaticFieldTest.php | 93 +++++++- .../SearchAdapter/Query/Builder/SortTest.php | 212 ++++++++++++++++++ .../SearchAdapter/ResponseFactoryTest.php | 9 +- app/code/Magento/Elasticsearch/etc/di.xml | 99 +++++++- ...kProductCollectionBeforeToHtmlObserver.php | 4 +- .../Search/Adapter/Mysql/Adapter.php | 1 + .../Search/Adapter/Mysql/ResponseFactory.php | 3 +- .../Magento/Framework/Search/Request.php | 18 +- .../Framework/Search/Request/Binder.php | 3 + .../Framework/Search/Request/Builder.php | 54 ++++- .../Framework/Search/RequestInterface.php | 7 + .../Search/Response/QueryResponse.php | 17 +- .../Framework/Search/ResponseInterface.php | 7 + .../Magento/Framework/Search/Search.php | 1 + .../Search/SearchResponseBuilder.php | 2 +- .../Test/Unit/Adapter/Mysql/AdapterTest.php | 1 + .../Adapter/Mysql/ResponseFactoryTest.php | 3 +- .../Test/Unit/Response/QueryResponseTest.php | 1 + .../Test/Unit/SearchResponseBuilderTest.php | 5 +- 57 files changed, 1759 insertions(+), 169 deletions(-) create mode 100644 app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategy.php create mode 100644 app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyInterface.php create mode 100644 app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyProvider.php create mode 100644 app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php create mode 100644 app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverInterface.php create mode 100644 app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php create mode 100644 app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplierInterface.php create mode 100644 app/code/Magento/CatalogSearch/Model/Search/ItemCollectionProvider.php create mode 100644 app/code/Magento/CatalogSearch/Model/Search/ItemCollectionProviderInterface.php create mode 100644 app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php create mode 100644 app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php create mode 100644 app/code/Magento/Elasticsearch/Model/ResourceModel/Advanced/Collection.php create mode 100644 app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection.php create mode 100644 app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php create mode 100644 app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php create mode 100644 app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php create mode 100644 app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php diff --git a/app/code/Magento/Catalog/Block/Product/ListProduct.php b/app/code/Magento/Catalog/Block/Product/ListProduct.php index c1b255c762dbb..0126d546d0ba2 100644 --- a/app/code/Magento/Catalog/Block/Product/ListProduct.php +++ b/app/code/Magento/Catalog/Block/Product/ListProduct.php @@ -188,7 +188,9 @@ protected function _beforeToHtml() $this->addToolbarBlock($collection); - $collection->load(); + if (!$collection->isLoaded()){ + $collection->load(); + } return parent::_beforeToHtml(); } diff --git a/app/code/Magento/CatalogSearch/Model/Advanced.php b/app/code/Magento/CatalogSearch/Model/Advanced.php index af0e9ff5528cf..5b96a8c21cbea 100644 --- a/app/code/Magento/CatalogSearch/Model/Advanced.php +++ b/app/code/Magento/CatalogSearch/Model/Advanced.php @@ -6,6 +6,8 @@ namespace Magento\CatalogSearch\Model; use Magento\Catalog\Model\Config; +use Magento\CatalogSearch\Model\Advanced\ProductCollectionPrepareStrategyProvider; +use Magento\CatalogSearch\Model\Search\ItemCollectionProviderInterface; use Magento\Catalog\Model\Product\Visibility; use Magento\Catalog\Model\ProductFactory; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; @@ -19,6 +21,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Registry; use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\App\ObjectManager; /** * Catalog advanced search model @@ -64,6 +67,7 @@ class Advanced extends \Magento\Framework\Model\AbstractModel /** * Initialize dependencies * + * @deprecated * @var Config */ protected $_catalogConfig; @@ -106,10 +110,22 @@ class Advanced extends \Magento\Framework\Model\AbstractModel /** * Advanced Collection Factory * + * @deprecated + * @see $collectionProvider * @var ProductCollectionFactory */ protected $productCollectionFactory; + /** + * @var ItemCollectionProviderInterface + */ + private $collectionProvider; + + /** + * @var ProductCollectionPrepareStrategyProvider|null + */ + private $productCollectionPrepareStrategyProvider; + /** * Construct * @@ -124,7 +140,8 @@ class Advanced extends \Magento\Framework\Model\AbstractModel * @param ProductCollectionFactory $productCollectionFactory * @param AdvancedFactory $advancedFactory * @param array $data - * + * @param ItemCollectionProviderInterface|null $collectionProvider + * @param ProductCollectionPrepareStrategyProvider|null $productCollectionPrepareStrategyProvider * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -138,7 +155,9 @@ public function __construct( StoreManagerInterface $storeManager, ProductCollectionFactory $productCollectionFactory, AdvancedFactory $advancedFactory, - array $data = [] + array $data = [], + ItemCollectionProviderInterface $collectionProvider = null, + ProductCollectionPrepareStrategyProvider $productCollectionPrepareStrategyProvider = null ) { $this->_attributeCollectionFactory = $attributeCollectionFactory; $this->_catalogProductVisibility = $catalogProductVisibility; @@ -147,11 +166,14 @@ public function __construct( $this->_productFactory = $productFactory; $this->_storeManager = $storeManager; $this->productCollectionFactory = $productCollectionFactory; + $this->collectionProvider = $collectionProvider; + $this->productCollectionPrepareStrategyProvider = $productCollectionPrepareStrategyProvider + ?: ObjectManager::getInstance()->get(ProductCollectionPrepareStrategyProvider::class); parent::__construct( $context, $registry, $advancedFactory->create(), - $this->productCollectionFactory->create(), + $this->resolveProductCollection(), $data ); } @@ -266,7 +288,7 @@ public function getAttributes() public function getProductCollection() { if ($this->_productCollection === null) { - $collection = $this->productCollectionFactory->create(); + $collection = $this->resolveProductCollection(); $this->prepareProductCollection($collection); if (!$collection) { return $collection; @@ -277,6 +299,18 @@ public function getProductCollection() return $this->_productCollection; } + /** + * Resolve product collection. + * + * @return \Magento\Catalog\Model\ResourceModel\Product\Collection|\Magento\Framework\Data\Collection + */ + private function resolveProductCollection() + { + return (null === $this->collectionProvider) + ? $this->productCollectionFactory->create() + : $this->collectionProvider->getCollection(); + } + /** * Prepare product collection * @@ -285,13 +319,7 @@ public function getProductCollection() */ public function prepareProductCollection($collection) { - $collection - ->addAttributeToSelect($this->_catalogConfig->getProductAttributes()) - ->setStore($this->_storeManager->getStore()) - ->addMinimalPrice() - ->addTaxPercents() - ->addStoreFilter() - ->setVisibility($this->_catalogProductVisibility->getVisibleInSearchIds()); + $this->productCollectionPrepareStrategyProvider->getStrategy()->prepare($collection); return $this; } diff --git a/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategy.php b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategy.php new file mode 100644 index 0000000000000..e62de9c689fc2 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategy.php @@ -0,0 +1,61 @@ +catalogConfig = $catalogConfig; + $this->storeManager = $storeManager; + $this->catalogProductVisibility = $catalogProductVisibility; + } + + /** + * @inheritdoc + */ + public function prepare(Collection $collection) + { + $collection + ->addAttributeToSelect($this->catalogConfig->getProductAttributes()) + ->setStore($this->storeManager->getStore()) + ->addMinimalPrice() + ->addTaxPercents() + ->addStoreFilter() + ->setVisibility($this->catalogProductVisibility->getVisibleInSearchIds()); + } +} diff --git a/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyInterface.php b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyInterface.php new file mode 100644 index 0000000000000..23719a6713a32 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyInterface.php @@ -0,0 +1,22 @@ +engineResolver = $engineResolver; + $this->strategies = $strategies; + } + + /** + * @return ProductCollectionPrepareStrategyInterface + */ + public function getStrategy(): ProductCollectionPrepareStrategyInterface + { + if (!isset($this->strategies[$this->engineResolver->getCurrentSearchEngine()])) { + throw new \DomainException('Undefined strategy ' . $this->engineResolver->getCurrentSearchEngine()); + } + return $this->strategies[$this->engineResolver->getCurrentSearchEngine()]; + } +} diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php index b4b15554f6029..80d5a1d61798f 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php @@ -6,15 +6,20 @@ namespace Magento\CatalogSearch\Model\ResourceModel\Advanced; use Magento\Catalog\Model\Product; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\Search\SearchCriteriaBuilder; use Magento\Framework\Api\Search\SearchResultFactory; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Search\Adapter\Mysql\TemporaryStorage; use Magento\Framework\Search\Request\EmptyRequestDataException; use Magento\Framework\Search\Request\NonExistingRequestNameException; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolver; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Api\Search\SearchResultInterface; /** * Advanced search collection @@ -59,6 +64,26 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection */ private $filterBuilder; + /** + * @var \Magento\Framework\Api\Search\SearchResultInterface + */ + private $searchResult; + + /** + * @var string + */ + private $searchRequestName; + + /** + * @var SearchCriteriaResolverFactory + */ + private $searchCriteriaResolverFactory; + + /** + * @var SearchResultApplierFactory + */ + private $searchResultApplierFactory; + /** * Collection constructor * @@ -88,7 +113,9 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param SearchResultFactory|null $searchResultFactory * @param ProductLimitationFactory|null $productLimitationFactory * @param MetadataPool|null $metadataPool - * + * @param string $searchRequestName + * @param SearchCriteriaResolverFactory|null $searchCriteriaResolverFactory + * @param SearchResultApplierFactory|null $searchResultApplierFactory * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -117,15 +144,23 @@ public function __construct( \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, SearchResultFactory $searchResultFactory = null, ProductLimitationFactory $productLimitationFactory = null, - MetadataPool $metadataPool = null + MetadataPool $metadataPool = null, + $searchRequestName = 'advanced_search_container', + SearchCriteriaResolverFactory $searchCriteriaResolverFactory = null, + SearchResultApplierFactory $searchResultApplierFactory = null ) { $this->requestBuilder = $requestBuilder; $this->searchEngine = $searchEngine; $this->temporaryStorageFactory = $temporaryStorageFactory; + $this->searchRequestName = $searchRequestName; if ($searchResultFactory === null) { $this->searchResultFactory = \Magento\Framework\App\ObjectManager::getInstance() ->get(\Magento\Framework\Api\Search\SearchResultFactory::class); } + $this->searchCriteriaResolverFactory = $searchCriteriaResolverFactory ?: ObjectManager::getInstance() + ->get(SearchCriteriaResolverFactory::class); + $this->searchResultApplierFactory = $searchResultApplierFactory ?: ObjectManager::getInstance() + ->get(SearchResultApplierFactory::class); parent::__construct( $entityFactory, $logger, @@ -172,6 +207,9 @@ public function addFieldsToFilter($fields) */ protected function _renderFiltersBefore() { + if ($this->isLoaded()) { + return; + } if ($this->filters) { foreach ($this->filters as $attributes) { foreach ($attributes as $attributeCode => $attributeValue) { @@ -179,33 +217,58 @@ protected function _renderFiltersBefore() $this->addAttributeToSearch($attributeCode, $attributeValue); } } - $searchCriteria = $this->getSearchCriteriaBuilder()->create(); - $searchCriteria->setRequestName('advanced_search_container'); + $searchCriteria = $this->getSearchCriteriaResolver()->resolve(); try { - $searchResult = $this->getSearch()->search($searchCriteria); + $this->searchResult = $this->getSearch()->search($searchCriteria); + $this->_totalRecords = $this->searchResult->getTotalCount(); } catch (EmptyRequestDataException $e) { /** @var \Magento\Framework\Api\Search\SearchResultInterface $searchResult */ - $searchResult = $this->searchResultFactory->create()->setItems([]); + $this->searchResult = $this->searchResultFactory->create()->setItems([]); } catch (NonExistingRequestNameException $e) { $this->_logger->error($e->getMessage()); throw new LocalizedException( __('An error occurred. For details, see the error log.') ); } - $temporaryStorage = $this->temporaryStorageFactory->create(); - $table = $temporaryStorage->storeApiDocuments($searchResult->getItems()); - - $this->getSelect()->joinInner( - [ - 'search_result' => $table->getName(), - ], - 'e.entity_id = search_result.' . TemporaryStorage::FIELD_ENTITY_ID, - [] - ); + $this->getSearchResultApplier($this->searchResult)->apply(); } parent::_renderFiltersBefore(); } + /** + * Clean order data. + */ + public function cleanOrder() + { + $this->_orders = []; + } + + /** + * @return SearchCriteriaResolver + */ + private function getSearchCriteriaResolver() + { + return $this->searchCriteriaResolverFactory->create([ + 'builder' => $this->getSearchCriteriaBuilder(), + 'collection' => $this, + 'searchRequestName' => $this->searchRequestName, + 'size' => $this->getPageSize(), + 'orders' => $this->_orders, + ]); + } + + /** + * @param SearchResultInterface $searchResult + * @return SearchResultApplierInterface + */ + private function getSearchResultApplier(SearchResultInterface $searchResult) + { + return $this->searchResultApplierFactory->create([ + 'collection' => $this, + 'searchResult' => $searchResult, + ]); + } + /** * Get attribute code. * diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index e6cfe8ca112f7..0b935638e93a7 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -3,8 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\CatalogSearch\Model\ResourceModel\Fulltext; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolver; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplier; +use Magento\Framework\Data\Collection\Db\SizeResolverInterfaceFactory; +use Magento\Framework\Api\Search\SearchResultInterface; +use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; +use Magento\Framework\Indexer\DimensionFactory; use Magento\CatalogSearch\Model\Search\RequestGenerator; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; @@ -60,11 +70,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection */ private $queryText; - /** - * @var string|null - */ - private $relevanceOrderDirection = null; - /** * @var string */ @@ -101,6 +106,16 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection */ private $filterBuilder; + /** + * @var SearchCriteriaResolverFactory + */ + private $searchCriteriaResolverFactory; + + /** + * @var SearchResultApplierFactory + */ + private $searchResultApplierFactory; + /** * Collection constructor * @@ -132,7 +147,14 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param SearchResultFactory|null $searchResultFactory * @param ProductLimitationFactory|null $productLimitationFactory * @param MetadataPool|null $metadataPool - * + * @param \Magento\Search\Api\SearchInterface|null $search + * @param \Magento\Framework\Api\Search\SearchCriteriaBuilder|null $searchCriteriaBuilder + * @param \Magento\Framework\Api\FilterBuilder|null $filterBuilder + * @param TableMaintainer|null $tableMaintainer + * @param PriceTableResolver|null $priceTableResolver + * @param DimensionFactory|null $dimensionFactory + * @param SearchCriteriaResolverFactory|null $searchCriteriaResolverFactory + * @param SearchResultApplierFactory|null $searchResultApplierFactory * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -163,7 +185,12 @@ public function __construct( $searchRequestName = 'catalog_view_container', SearchResultFactory $searchResultFactory = null, ProductLimitationFactory $productLimitationFactory = null, - MetadataPool $metadataPool = null + MetadataPool $metadataPool = null, + \Magento\Search\Api\SearchInterface $search = null, + \Magento\Framework\Api\Search\SearchCriteriaBuilder $searchCriteriaBuilder = null, + \Magento\Framework\Api\FilterBuilder $filterBuilder = null, + SearchCriteriaResolverFactory $searchCriteriaResolverFactory = null, + SearchResultApplierFactory $searchResultApplierFactory = null ) { $this->queryFactory = $catalogSearchData; if ($searchResultFactory === null) { @@ -198,6 +225,15 @@ public function __construct( $this->searchEngine = $searchEngine; $this->temporaryStorageFactory = $temporaryStorageFactory; $this->searchRequestName = $searchRequestName; + $this->search = $search ?: ObjectManager::getInstance()->get(\Magento\Search\Api\SearchInterface::class); + $this->searchCriteriaBuilder = $searchCriteriaBuilder ?: ObjectManager::getInstance() + ->get(\Magento\Framework\Api\Search\SearchCriteriaBuilder::class); + $this->filterBuilder = $filterBuilder ?: ObjectManager::getInstance() + ->get(\Magento\Framework\Api\FilterBuilder::class); + $this->searchCriteriaResolverFactory = $searchCriteriaResolverFactory ?: ObjectManager::getInstance() + ->get(SearchCriteriaResolverFactory::class); + $this->searchResultApplierFactory = $searchResultApplierFactory ?: ObjectManager::getInstance() + ->get(SearchResultApplierFactory::class); } /** @@ -333,30 +369,17 @@ public function addSearchFilter($query) */ protected function _renderFiltersBefore() { - $this->getSearchCriteriaBuilder(); - $this->getFilterBuilder(); - $this->getSearch(); - - if ($this->queryText) { - $this->filterBuilder->setField('search_term'); - $this->filterBuilder->setValue($this->queryText); - $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create()); + if ($this->isLoaded()) { + return; } - $priceRangeCalculation = $this->_scopeConfig->getValue( - \Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory::XML_PATH_RANGE_CALCULATION, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); - if ($priceRangeCalculation) { - $this->filterBuilder->setField('price_dynamic_algorithm'); - $this->filterBuilder->setValue($priceRangeCalculation); - $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create()); - } + $this->prepareSearchTermFilter(); + $this->preparePriceAggregation(); - $searchCriteria = $this->searchCriteriaBuilder->create(); - $searchCriteria->setRequestName($this->searchRequestName); + $searchCriteria = $this->getSearchCriteriaResolver()->resolve(); try { $this->searchResult = $this->getSearch()->search($searchCriteria); + $this->_totalRecords = $this->searchResult->getTotalCount(); } catch (EmptyRequestDataException $e) { /** @var \Magento\Framework\Api\Search\SearchResultInterface $searchResult */ $this->searchResult = $this->searchResultFactory->create()->setItems([]); @@ -365,23 +388,44 @@ protected function _renderFiltersBefore() throw new LocalizedException(__('An error occurred. For details, see the error log.')); } - $temporaryStorage = $this->temporaryStorageFactory->create(); - $table = $temporaryStorage->storeApiDocuments($this->searchResult->getItems()); + $this->getSearchResultApplier($this->searchResult)->apply(); - $this->getSelect()->joinInner( - [ - 'search_result' => $table->getName(), - ], - 'e.entity_id = search_result.' . TemporaryStorage::FIELD_ENTITY_ID, - [] - ); + parent::_renderFiltersBefore(); + } - if ($this->relevanceOrderDirection) { - $this->getSelect()->order( - 'search_result.'. TemporaryStorage::FIELD_SCORE . ' ' . $this->relevanceOrderDirection - ); - } - return parent::_renderFiltersBefore(); + /** + * Clean order data. + */ + public function cleanOrder() + { + $this->_orders = []; + } + + /** + * @return SearchCriteriaResolver + */ + private function getSearchCriteriaResolver() + { + return $this->searchCriteriaResolverFactory->create([ + 'builder' => $this->getSearchCriteriaBuilder(), + 'collection' => $this, + 'searchRequestName' => $this->searchRequestName, + 'currentPage' => $this->_curPage, + 'size' => $this->getPageSize(), + 'orders' => $this->_orders, + ]); + } + + /** + * @param SearchResultInterface $searchResult + * @return SearchResultApplier + */ + private function getSearchResultApplier(SearchResultInterface $searchResult) + { + return $this->searchResultApplierFactory->create([ + 'collection' => $this, + 'searchResult' => $searchResult, + ]); } /** @@ -419,11 +463,11 @@ protected function _renderFilters() public function setOrder($attribute, $dir = Select::SQL_DESC) { if ($attribute === 'relevance') { - $this->relevanceOrderDirection = $dir; - } else { - parent::setOrder($attribute, $dir); + $attribute = TemporaryStorage::FIELD_SCORE; } + parent::setOrder($attribute, $dir); + return $this; } @@ -487,4 +531,36 @@ public function setVisibility($visibility) $this->addFieldToFilter('visibility', $visibility); return parent::setVisibility($visibility); } + + /** + * Prepare search term filter for text query. + * + * @return void + */ + private function prepareSearchTermFilter(): void + { + if ($this->queryText) { + $this->filterBuilder->setField('search_term'); + $this->filterBuilder->setValue($this->queryText); + $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create()); + } + } + + /** + * Prepare price aggregation algorithm. + * + * @return void + */ + private function preparePriceAggregation(): void + { + $priceRangeCalculation = $this->_scopeConfig->getValue( + \Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory::XML_PATH_RANGE_CALCULATION, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + if ($priceRangeCalculation) { + $this->filterBuilder->setField('price_dynamic_algorithm'); + $this->filterBuilder->setValue($priceRangeCalculation); + $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create()); + } + } } diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php new file mode 100644 index 0000000000000..0c93ecb2627c7 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php @@ -0,0 +1,51 @@ +builder = $builder; + $this->searchRequestName = $searchRequestName; + } + + /** + * @return SearchCriteria + */ + public function resolve() : SearchCriteria + { + $searchCriteria = $this->builder->create(); + $searchCriteria->setRequestName($this->searchRequestName); + + return $searchCriteria; + } +} diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverInterface.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverInterface.php new file mode 100644 index 0000000000000..2ca6f6b617f8c --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverInterface.php @@ -0,0 +1,20 @@ +collection = $collection; + $this->searchResult = $searchResult; + $this->temporaryStorageFactory = $temporaryStorageFactory; + } + + /** + * @return void + */ + public function apply() + { + $temporaryStorage = $this->temporaryStorageFactory->create(); + $table = $temporaryStorage->storeApiDocuments($this->searchResult->getItems()); + + $this->collection->getSelect()->joinInner( + [ + 'search_result' => $table->getName(), + ], + 'e.entity_id = search_result.' . TemporaryStorage::FIELD_ENTITY_ID, + [] + ); + } +} diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplierInterface.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplierInterface.php new file mode 100644 index 0000000000000..111b6097632d3 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplierInterface.php @@ -0,0 +1,18 @@ +engineResolver = $engineResolver; + $this->factories = $factories; + } + + /** + * @return Collection|\Magento\Catalog\Model\ResourceModel\Product\Collection + */ + public function getCollection(): Collection + { + if (!isset($this->factories[$this->engineResolver->getCurrentSearchEngine()])) { + throw new \DomainException('Undefined factory ' . $this->engineResolver->getCurrentSearchEngine()); + } + return $this->factories[$this->engineResolver->getCurrentSearchEngine()]->create(); + } +} diff --git a/app/code/Magento/CatalogSearch/Model/Search/ItemCollectionProviderInterface.php b/app/code/Magento/CatalogSearch/Model/Search/ItemCollectionProviderInterface.php new file mode 100644 index 0000000000000..7614eece7b746 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Search/ItemCollectionProviderInterface.php @@ -0,0 +1,21 @@ + $productCollectionFactory, 'storeManager' => $this->storeManager, 'currencyFactory' => $currencyFactory, + 'collectionProvider' => null ] ); $instance->addFilters($values); diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php index b65a0d6ca47a0..b941008bae0ae 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php @@ -7,7 +7,11 @@ use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; use Magento\CatalogSearch\Test\Unit\Model\ResourceModel\BaseCollection; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; /** * Tests Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection @@ -79,6 +83,31 @@ protected function setUp() $productLimitationFactoryMock->method('create') ->willReturn($productLimitationMock); + $searchCriteriaResolver = $this->getMockBuilder(SearchCriteriaResolverInterface::class) + ->disableOriginalConstructor() + ->setMethods(['resolve']) + ->getMockForAbstractClass(); + $searchCriteriaResolverFactory = $this->getMockBuilder(SearchCriteriaResolverFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $searchCriteriaResolverFactory->expects($this->any()) + ->method('create') + ->willReturn($searchCriteriaResolver); + + $searchResultApplier = $this->getMockBuilder(SearchResultApplierInterface::class) + ->disableOriginalConstructor() + ->setMethods(['apply']) + ->getMockForAbstractClass(); + $searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $searchResultApplierFactory->expects($this->any()) + ->method('create') + ->willReturn($searchResultApplier); + + $this->advancedCollection = $this->objectManager->getObject( \Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection::class, [ @@ -90,6 +119,9 @@ protected function setUp() 'temporaryStorageFactory' => $this->temporaryStorageFactory, 'search' => $this->search, 'productLimitationFactory' => $productLimitationFactoryMock, + 'collectionProvider' => null, + 'searchCriteriaResolverFactory' => $searchCriteriaResolverFactory, + 'searchResultApplierFactory' => $searchResultApplierFactory ] ); } @@ -117,18 +149,8 @@ public function testLike() ->willReturn($this->filterBuilder); $filter = $this->createMock(\Magento\Framework\Api\Filter::class); - $this->filterBuilder->expects($this->once())->method('create')->willReturn($filter); - - $criteria = $this->createMock(\Magento\Framework\Api\Search\SearchCriteria::class); - $this->criteriaBuilder->expects($this->once())->method('create')->willReturn($criteria); - $criteria->expects($this->once()) - ->method('setRequestName') - ->with('advanced_search_container'); - - $tempTable = $this->createMock(\Magento\Framework\DB\Ddl\Table::class); - $temporaryStorage = $this->createMock(\Magento\Framework\Search\Adapter\Mysql\TemporaryStorage::class); - $temporaryStorage->expects($this->once())->method('storeApiDocuments')->willReturn($tempTable); - $this->temporaryStorageFactory->expects($this->once())->method('create')->willReturn($temporaryStorage); + $this->filterBuilder->expects($this->any())->method('create')->willReturn($filter); + $searchResult = $this->createMock(\Magento\Framework\Api\Search\SearchResultInterface::class); $this->search->expects($this->once())->method('search')->willReturn($searchResult); diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php index a3b1d2fd0f2b6..93ef42c8d5798 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php @@ -5,6 +5,10 @@ */ namespace Magento\CatalogSearch\Test\Unit\Model\ResourceModel\Fulltext; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; use Magento\CatalogSearch\Test\Unit\Model\ResourceModel\BaseCollection; use Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory; use PHPUnit_Framework_MockObject_MockObject as MockObject; @@ -97,6 +101,29 @@ protected function setUp() $temporaryStorageFactory->expects($this->any()) ->method('create') ->willReturn($this->temporaryStorage); + $searchCriteriaResolver = $this->getMockBuilder(SearchCriteriaResolverInterface::class) + ->disableOriginalConstructor() + ->setMethods(['resolve']) + ->getMockForAbstractClass(); + $searchCriteriaResolverFactory = $this->getMockBuilder(SearchCriteriaResolverFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $searchCriteriaResolverFactory->expects($this->any()) + ->method('create') + ->willReturn($searchCriteriaResolver); + + $searchResultApplier = $this->getMockBuilder(SearchResultApplierInterface::class) + ->disableOriginalConstructor() + ->setMethods(['apply']) + ->getMockForAbstractClass(); + $searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $searchResultApplierFactory->expects($this->any()) + ->method('create') + ->willReturn($searchResultApplier); $this->model = $this->objectManager->getObject( \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection::class, @@ -106,6 +133,8 @@ protected function setUp() 'scopeConfig' => $this->scopeConfig, 'temporaryStorageFactory' => $temporaryStorageFactory, 'productLimitationFactory' => $productLimitationFactoryMock, + 'searchCriteriaResolverFactory' => $searchCriteriaResolverFactory, + 'searchResultApplierFactory' => $searchResultApplierFactory ] ); @@ -124,37 +153,10 @@ protected function tearDown() $reflectionProperty->setValue(null); } - /** - * @expectedException \Exception - * @expectedExceptionCode 333 - * @expectedExceptionMessage setRequestName - */ - public function testGetFacetedDataWithException() - { - $criteria = $this->createMock(\Magento\Framework\Api\Search\SearchCriteria::class); - $this->criteriaBuilder->expects($this->once())->method('create')->willReturn($criteria); - $criteria->expects($this->once()) - ->method('setRequestName') - ->withConsecutive(['catalog_view_container']) - ->willThrowException(new \Exception('setRequestName', 333)); - $this->model->getFacetedData('field'); - } - public function testGetFacetedDataWithEmptyAggregations() { - $criteria = $this->createMock(\Magento\Framework\Api\Search\SearchCriteria::class); - $this->criteriaBuilder->expects($this->once())->method('create')->willReturn($criteria); - $criteria->expects($this->once()) - ->method('setRequestName') - ->withConsecutive(['catalog_view_container']); $searchResult = $this->getMockBuilder(\Magento\Framework\Api\Search\SearchResultInterface::class) ->getMockForAbstractClass(); - $table = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Table::class) - ->setMethods(['getName']) - ->getMock(); - $this->temporaryStorage->expects($this->once()) - ->method('storeApiDocuments') - ->willReturn($table); $this->search->expects($this->once()) ->method('search') ->willReturn($searchResult); diff --git a/app/code/Magento/CatalogSearch/etc/di.xml b/app/code/Magento/CatalogSearch/etc/di.xml index cc07384d4c525..26eede113be7c 100644 --- a/app/code/Magento/CatalogSearch/etc/di.xml +++ b/app/code/Magento/CatalogSearch/etc/di.xml @@ -14,6 +14,9 @@ + + + @@ -176,8 +179,13 @@ Magento\CatalogSearch\Model\ResourceModel\Fulltext\SearchCollectionFactory - + + + + Magento\CatalogSearch\Model\ResourceModel\Advanced\CollectionFactory + + + Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection @@ -185,6 +193,21 @@ Magento\CatalogSearch\Model\ResourceModel\Advanced\CollectionFactory + Magento\CatalogSearch\Model\Search\ItemCollectionProviderInterface + + + + + + Magento\CatalogSearch\Model\ResourceModel\Advanced\CollectionFactory + + + + + + + Magento\CatalogSearch\Model\Advanced\ProductCollectionPrepareStrategy + diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml index 83808df5b95e4..9f61136bb1893 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml +++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml @@ -11,6 +11,7 @@ /** * @var $block \Magento\CatalogSearch\Block\Advanced\Result */ +$productList = $block->getProductListHtml(); ?> getResultCount()): ?> getResultCount()): ?> - + getSearchCriterias(); ?> diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php index b7f21696162dd..26173fcf29b0c 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php @@ -19,13 +19,19 @@ class Converter implements ConverterInterface */ private const ES_NO_INDEX = false; + /** + * Text flags for Elasticsearch no analyze index value + */ + private const ES_NO_ANALYZE = false; + /** * Mapping between internal data types and elastic service. * * @var array */ private $mapping = [ - 'no_index' => self::ES_NO_INDEX, + ConverterInterface::INTERNAL_NO_INDEX_VALUE => self::ES_NO_INDEX, + ConverterInterface::INTERNAL_NO_ANALYZE_VALUE => self::ES_NO_ANALYZE, ]; /** diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php index a6838d831b4bc..0ae347d5791ad 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php @@ -125,6 +125,7 @@ public function query(RequestInterface $request) [ 'documents' => $rawDocuments, 'aggregations' => $aggregationBuilder->build($request, $rawResponse), + 'total' => isset($rawResponse['hits']['total']) ? $rawResponse['hits']['total'] : 0 ] ); return $queryResponse; diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php index db961d86962e9..be0d7d106a5dc 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php @@ -3,8 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Query; +use Magento\Elasticsearch\SearchAdapter\Query\Builder\Sort; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Search\RequestInterface; use Magento\Elasticsearch\Model\Config; use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; @@ -41,22 +44,30 @@ class Builder */ protected $scopeResolver; + /** + * @var Sort + */ + protected $sortBuilder; + /** * @param Config $clientConfig * @param SearchIndexNameResolver $searchIndexNameResolver * @param AggregationBuilder $aggregationBuilder * @param ScopeResolverInterface $scopeResolver + * @param Sort|null $sortBuilder */ public function __construct( Config $clientConfig, SearchIndexNameResolver $searchIndexNameResolver, AggregationBuilder $aggregationBuilder, - ScopeResolverInterface $scopeResolver + ScopeResolverInterface $scopeResolver, + Sort $sortBuilder = null ) { $this->clientConfig = $clientConfig; $this->searchIndexNameResolver = $searchIndexNameResolver; $this->aggregationBuilder = $aggregationBuilder; $this->scopeResolver = $scopeResolver; + $this->sortBuilder = $sortBuilder ?: ObjectManager::getInstance()->get(Sort::class); } /** @@ -70,6 +81,7 @@ public function initQuery(RequestInterface $request) { $dimension = current($request->getDimensions()); $storeId = $this->scopeResolver->getScope($dimension->getValue())->getId(); + $searchQuery = [ 'index' => $this->searchIndexNameResolver->getIndexName($storeId, $request->getIndex()), 'type' => $this->clientConfig->getEntityType(), @@ -77,6 +89,7 @@ public function initQuery(RequestInterface $request) 'from' => $request->getFrom(), 'size' => $request->getSize(), 'stored_fields' => ['_id', '_score'], + 'sort' => $this->sortBuilder->getSort($request), 'query' => [], ], ]; diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php index 17ebf9c83903e..54586fa357ff0 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php @@ -146,6 +146,16 @@ public function getAttributeCode(): string return $this->attributeCode; } + /** + * Check if attribute is sortable. + * + * @return bool + */ + public function isSortable(): bool + { + return (int)$this->getAttribute()->getUsedForSortBy() === 1; + } + /** * Check if attribute is defined by user. * diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php index 5abe4972e34d0..535ab62dd5991 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php @@ -17,13 +17,19 @@ class Converter implements ConverterInterface */ private const ES_NO_INDEX = 'no'; + /** + * Text flags for Elasticsearch no analyze index value + */ + private const ES_NO_ANALYZE = 'not_analyzed'; + /** * Mapping between internal data types and elastic service. * * @var array */ private $mapping = [ - 'no_index' => self::ES_NO_INDEX, + ConverterInterface::INTERNAL_NO_INDEX_VALUE => self::ES_NO_INDEX, + ConverterInterface::INTERNAL_NO_ANALYZE_VALUE => self::ES_NO_ANALYZE, ]; /** diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/ConverterInterface.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/ConverterInterface.php index 02c4d29558dad..5ecfd62430032 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/ConverterInterface.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/ConverterInterface.php @@ -17,6 +17,7 @@ interface ConverterInterface */ public const INTERNAL_NO_INDEX_VALUE = 'no_index'; public const INTERNAL_INDEX_VALUE = 'index'; + public const INTERNAL_NO_ANALYZE_VALUE = 'no_analyze'; /** * Get service field index type. diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php index 3208ca7fc6171..5f319daeb3b39 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php @@ -51,22 +51,22 @@ public function __construct( */ public function getFieldName(AttributeAdapter $attribute, $context = []): ?string { - $fieldType = $this->fieldTypeResolver->getFieldType($attribute); $attributeCode = $attribute->getAttributeCode(); - $frontendInput = $attribute->getFrontendInput(); if (empty($context['type'])) { - $fieldName = $attributeCode; - } elseif ($context['type'] === FieldMapperInterface::TYPE_FILTER) { + return $attributeCode; + } + + $fieldType = $this->fieldTypeResolver->getFieldType($attribute); + $frontendInput = $attribute->getFrontendInput(); + $fieldName = $attributeCode; + if ($context['type'] === FieldMapperInterface::TYPE_FILTER) { if ($this->isStringServiceFieldType($fieldType)) { - return $this->getFieldName( - $attribute, - array_merge($context, ['type' => FieldMapperInterface::TYPE_QUERY]) - ); + return $this->getQueryTypeFieldName($frontendInput, $fieldType, $attributeCode); } $fieldName = $attributeCode; } elseif ($context['type'] === FieldMapperInterface::TYPE_QUERY) { $fieldName = $this->getQueryTypeFieldName($frontendInput, $fieldType, $attributeCode); - } else { + } elseif ($context['type'] == FieldMapperInterface::TYPE_SORT && $attribute->isSortable()) { $fieldName = 'sort_' . $attributeCode; } @@ -115,10 +115,11 @@ private function getQueryTypeFieldName($frontendInput, $fieldType, $attributeCod private function getRefinedFieldName($frontendInput, $fieldType, $attributeCode) { $stringTypeKey = $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_STRING); + $keywordTypeKey = $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD); switch ($frontendInput) { case 'select': case 'multiselect': - return in_array($fieldType, [$stringTypeKey, 'integer'], true) + return in_array($fieldType, [$stringTypeKey, $keywordTypeKey, 'integer'], true) ? $attributeCode . '_value' : $attributeCode; case 'boolean': diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/SpecialAttribute.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/SpecialAttribute.php index 4fa16db98639e..652fc853adbcc 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/SpecialAttribute.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/SpecialAttribute.php @@ -24,7 +24,9 @@ class SpecialAttribute implements ResolverInterface */ public function getFieldName(AttributeAdapter $attribute, $context = []): ?string { - if (in_array($attribute->getAttributeCode(), ['id', 'sku', 'store_id', 'visibility'], true)) { + if (in_array($attribute->getAttributeCode(), ['id', 'sku', 'store_id', 'visibility'], true) + && empty($context['type']) + ) { return $attribute->getAttributeCode(); } diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php index b5c78cbc28f45..f5847d8643d0a 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php @@ -7,6 +7,9 @@ namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface + as FieldNameResolver; +use Magento\Framework\App\ObjectManager; use Magento\Eav\Model\Config; use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; @@ -19,6 +22,7 @@ as FieldTypeResolver; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\ResolverInterface as FieldIndexResolver; +use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; /** * Provide static fields for mapping of product. @@ -55,6 +59,11 @@ class StaticField implements FieldProviderInterface */ private $fieldIndexResolver; + /** + * @var FieldNameResolver + */ + private $fieldNameResolver; + /** * @param Config $eavConfig * @param FieldTypeConverterInterface $fieldTypeConverter @@ -62,6 +71,7 @@ class StaticField implements FieldProviderInterface * @param FieldTypeResolver $fieldTypeResolver * @param FieldIndexResolver $fieldIndexResolver * @param AttributeProvider $attributeAdapterProvider + * @param FieldNameResolver|null $fieldNameResolver */ public function __construct( Config $eavConfig, @@ -69,7 +79,8 @@ public function __construct( IndexTypeConverterInterface $indexTypeConverter, FieldTypeResolver $fieldTypeResolver, FieldIndexResolver $fieldIndexResolver, - AttributeProvider $attributeAdapterProvider + AttributeProvider $attributeAdapterProvider, + FieldNameResolver $fieldNameResolver = null ) { $this->eavConfig = $eavConfig; $this->fieldTypeConverter = $fieldTypeConverter; @@ -77,6 +88,8 @@ public function __construct( $this->fieldTypeResolver = $fieldTypeResolver; $this->fieldIndexResolver = $fieldIndexResolver; $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() + ->get(FieldNameResolver::class); } /** @@ -92,19 +105,38 @@ public function getFields(array $context = []): array foreach ($attributes as $attribute) { $attributeAdapter = $this->attributeAdapterProvider->getByAttributeCode($attribute->getAttributeCode()); - $code = $attributeAdapter->getAttributeCode(); + $fieldName = $this->fieldNameResolver->getFieldName($attributeAdapter); - $allAttributes[$code] = [ + $allAttributes[$fieldName] = [ 'type' => $this->fieldTypeResolver->getFieldType($attributeAdapter), ]; $index = $this->fieldIndexResolver->getFieldIndex($attributeAdapter); if (null !== $index) { - $allAttributes[$code]['index'] = $index; + $allAttributes[$fieldName]['index'] = $index; + } + + if ($attributeAdapter->isSortable()) { + $sortFieldName = $this->fieldNameResolver->getFieldName( + $attributeAdapter, + ['type' => FieldMapperInterface::TYPE_SORT] + ); + $allAttributes[$fieldName]['fields'][$sortFieldName] = [ + 'type' => $this->fieldTypeConverter->convert( + FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD + ), + 'index' => $this->indexTypeConverter->convert( + IndexTypeConverterInterface::INTERNAL_NO_ANALYZE_VALUE + ) + ]; } if ($attributeAdapter->isComplexType()) { - $allAttributes[$code . '_value'] = [ + $childFieldName = $this->fieldNameResolver->getFieldName( + $attributeAdapter, + ['type' => FieldMapperInterface::TYPE_QUERY] + ); + $allAttributes[$childFieldName] = [ 'type' => $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_STRING) ]; } diff --git a/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php b/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php new file mode 100644 index 0000000000000..b3f8a56110f8d --- /dev/null +++ b/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php @@ -0,0 +1,41 @@ +catalogConfig = $catalogConfig; + } + + /** + * @inheritdoc + */ + public function prepare(Collection $collection) + { + $collection + ->addAttributeToSelect($this->catalogConfig->getProductAttributes()) + ->addMinimalPrice() + ->addTaxPercents(); + } +} diff --git a/app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php b/app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php new file mode 100644 index 0000000000000..16f0da24e3931 --- /dev/null +++ b/app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php @@ -0,0 +1,51 @@ +engineResolver = $engineResolver; + $this->factories = $factories; + } + + /** + * @param \Magento\Catalog\Model\Category $category + * @return \Magento\Catalog\Model\ResourceModel\Product\Collection + */ + public function getCollection(\Magento\Catalog\Model\Category $category) + { + if (!isset($this->factories[$this->engineResolver->getCurrentSearchEngine()])) { + throw new \DomainException('Undefined factory ' . $this->engineResolver->getCurrentSearchEngine()); + } + return $this->factories[$this->engineResolver->getCurrentSearchEngine()]->create(); + } +} diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Advanced/Collection.php new file mode 100644 index 0000000000000..15b1aab9edae7 --- /dev/null +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Advanced/Collection.php @@ -0,0 +1,51 @@ +addFieldToFilter('category_ids', $category->getId()); + $this->_productLimitationPrice(); + + return $this; + } + + /** + * @inheritdoc + */ + public function setVisibility($visibility) + { + $this->addFieldToFilter('visibility', $visibility); + return $this; + } +} diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection.php new file mode 100644 index 0000000000000..767f5d538a08d --- /dev/null +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection.php @@ -0,0 +1,54 @@ +addFieldToFilter('category_ids', $category->getId()); + $this->_productLimitationPrice(); + + return $this; + } + + /** + * @inheritdoc + */ + public function setVisibility($visibility) + { + $this->addFieldToFilter('visibility', $visibility); + return $this; + } + + /** + * Set Order field + * + * @param string $attribute + * @param string $dir + * @return $this + */ + public function setOrder($attribute, $dir = Select::SQL_DESC) + { + if (is_array($attribute)) { + parent::setOrder($attribute, $dir); + } + parent::addOrder($attribute, $dir); + + return $this; + } +} diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php new file mode 100644 index 0000000000000..7115252c43bdc --- /dev/null +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php @@ -0,0 +1,87 @@ +builder = $builder; + $this->collection = $collection; + $this->searchRequestName = $searchRequestName; + $this->currentPage = $currentPage; + $this->size = $size; + $this->orders = $orders; + } + + /** + * @return SearchCriteria + */ + public function resolve(): SearchCriteria + { + $this->builder->setPageSize($this->size); + $searchCriteria = $this->builder->create(); + $searchCriteria->setRequestName($this->searchRequestName); + $searchCriteria->setSortOrders($this->orders); + $searchCriteria->setCurrentPage($this->currentPage - 1); + + return $searchCriteria; + } +} diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php new file mode 100644 index 0000000000000..4b76e56bb0526 --- /dev/null +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php @@ -0,0 +1,60 @@ +collection = $collection; + $this->searchResult = $searchResult; + } + + /** + * @return void + */ + public function apply() + { + if (empty($this->searchResult->getItems())) { + $this->collection->getSelect()->where('NULL'); + return; + } + $ids = []; + foreach ($this->searchResult->getItems() as $item) { + $ids[] = (int)$item->getId(); + } + $this->collection->setPageSize(null); + $this->collection->cleanOrder(); + $this->collection->getSelect()->where('e.entity_id IN (?)', $ids); + $orderList = join(',', $ids); + $this->collection->getSelect()->reset(\Magento\Framework\DB\Select::ORDER); + $this->collection->getSelect()->order("FIELD(e.entity_id,${orderList})"); + } +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php b/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php index 43b2bfe553a98..bf5f00781dae7 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php @@ -86,6 +86,7 @@ public function query(RequestInterface $request) [ 'documents' => $rawDocuments, 'aggregations' => $aggregationBuilder->build($request, $rawResponse), + 'total' => isset($rawResponse['hits']['total']) ? $rawResponse['hits']['total'] : 0 ] ); return $queryResponse; diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php index cf75366b9b25e..5108f03026d19 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php @@ -3,10 +3,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Elasticsearch\SearchAdapter\Query; use Magento\Framework\Search\RequestInterface; -use Magento\Framework\App\ScopeResolverInterface; use Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Query\Builder as Elasticsearch5Builder; /** @@ -15,7 +15,6 @@ */ class Builder extends Elasticsearch5Builder { - /** * Set initial settings for query * @@ -34,6 +33,7 @@ public function initQuery(RequestInterface $request) 'from' => $request->getFrom(), 'size' => $request->getSize(), 'fields' => ['_id', '_score'], + 'sort' => $this->sortBuilder->getSort($request), 'query' => [], ], ]; diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php new file mode 100644 index 0000000000000..3b12a02190657 --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php @@ -0,0 +1,104 @@ + '_score', + ]; + + /** + * @var AttributeProvider + */ + private $attributeAdapterProvider; + + /** + * @var FieldNameResolver + */ + private $fieldNameResolver; + + /** + * @var array + */ + private $skippedFields; + + /** + * @var array + */ + private $map; + + /** + * @param AttributeProvider $attributeAdapterProvider + * @param FieldNameResolver $fieldNameResolver + * @param array $skippedFields + * @param array $map + */ + public function __construct( + AttributeProvider $attributeAdapterProvider, + FieldNameResolver $fieldNameResolver, + array $skippedFields = [], + array $map = [] + ) { + $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->fieldNameResolver = $fieldNameResolver; + $this->skippedFields = array_merge(self::DEFAULT_SKIPPED_FIELDS, $skippedFields); + $this->map = array_merge(self::DEFAULT_MAP, $map); + } + + /** + * Prepare sort. + * + * @param RequestInterface $request + * @return array + */ + public function getSort(RequestInterface $request) + { + $sorts = []; + foreach ($request->getSort() as $item) { + if (in_array($item['field'], $this->skippedFields)) { + continue; + } + $attribute = $this->attributeAdapterProvider->getByAttributeCode($item['field']); + $fieldName = $this->fieldNameResolver->getFieldName($attribute); + if (isset($this->map[$fieldName])) { + $fieldName = $this->map[$fieldName]; + } + if ($attribute->isSortable() && !($attribute->isFloatType() || $attribute->isIntegerType())) { + $fieldName .= '.' . $this->fieldNameResolver->getFieldName( + $attribute, + ['type' => FieldMapperInterface::TYPE_SORT] + ); + } + $sorts[] = [ + $fieldName => [ + 'order' => strtolower($item['direction']) + ] + ]; + } + + return $sorts; + } +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/ResponseFactory.php b/app/code/Magento/Elasticsearch/SearchAdapter/ResponseFactory.php index 33fda48f4af57..0813975ac9a4b 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/ResponseFactory.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/ResponseFactory.php @@ -76,6 +76,7 @@ public function create($response) [ 'documents' => $documents, 'aggregations' => $aggregations, + 'total' => $response['total'] ] ); } diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php index 4fa99f3bf834d..fd5c87bc9518b 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php @@ -66,6 +66,7 @@ protected function setUp() * @param $fieldType * @param $attributeCode * @param $frontendInput + * @param $isSortable * @param $context * @param $expected * @return void @@ -74,6 +75,7 @@ public function testGetFieldName( $fieldType, $attributeCode, $frontendInput, + $isSortable, $context, $expected ) { @@ -82,7 +84,7 @@ public function testGetFieldName( ->willReturn('string'); $attributeMock = $this->getMockBuilder(AttributeAdapter::class) ->disableOriginalConstructor() - ->setMethods(['getAttributeCode', 'getFrontendInput']) + ->setMethods(['getAttributeCode', 'getFrontendInput', 'isSortable']) ->getMock(); $attributeMock->expects($this->any()) ->method('getAttributeCode') @@ -90,6 +92,9 @@ public function testGetFieldName( $attributeMock->expects($this->any()) ->method('getFrontendInput') ->willReturn($frontendInput); + $attributeMock->expects($this->any()) + ->method('isSortable') + ->willReturn($isSortable); $this->fieldTypeResolver->expects($this->any()) ->method('getFieldType') ->willReturn($fieldType); @@ -106,13 +111,13 @@ public function testGetFieldName( public function getFieldNameProvider() { return [ - ['', 'code', '', [], 'code'], - ['', 'code', '', ['type' => 'default'], 'code'], - ['string', '*', '', ['type' => 'default'], '_all'], - ['', 'code', '', ['type' => 'default'], 'code'], - ['', 'code', 'select', ['type' => 'default'], 'code'], - ['', 'code', 'boolean', ['type' => 'default'], 'code'], - ['', 'code', '', ['type' => 'type'], 'sort_code'], + ['', 'code', '', false, [], 'code'], + ['', 'code', '', false, ['type' => 'default'], 'code'], + ['string', '*', '', false, ['type' => 'default'], '_all'], + ['', 'code', '', false, ['type' => 'default'], 'code'], + ['', 'code', 'select', false, ['type' => 'default'], 'code'], + ['', 'code', 'boolean', false, ['type' => 'default'], 'code'], + ['', 'code', '', true, ['type' => 'sort'], 'sort_code'], ]; } } diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php index bf8b601ed43ab..de85b8b6602b8 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php @@ -20,6 +20,8 @@ as FieldTypeResolver; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\ResolverInterface as FieldIndexResolver; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface + as FieldNameResolver; /** * @SuppressWarnings(PHPMD) @@ -61,6 +63,11 @@ class StaticFieldTest extends \PHPUnit\Framework\TestCase */ private $fieldTypeResolver; + /** + * @var FieldNameResolver + */ + private $fieldNameResolver; + /** * Set up test environment * @@ -90,6 +97,10 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['getFieldIndex']) ->getMock(); + $this->fieldNameResolver = $this->getMockBuilder(FieldNameResolver::class) + ->disableOriginalConstructor() + ->setMethods(['getFieldName']) + ->getMock(); $objectManager = new ObjectManagerHelper($this); @@ -102,6 +113,7 @@ protected function setUp() 'attributeAdapterProvider' => $this->attributeAdapterProvider, 'fieldIndexResolver' => $this->fieldIndexResolver, 'fieldTypeResolver' => $this->fieldTypeResolver, + 'fieldNameResolver' => $this->fieldNameResolver, ] ); } @@ -113,6 +125,10 @@ protected function setUp() * @param $indexType * @param $isComplexType * @param $complexType + * @param $isSortable + * @param $fieldName + * @param $compositeFieldName + * @param $sortFieldName * @param array $expected * @return void */ @@ -122,6 +138,10 @@ public function testGetAllAttributesTypes( $indexType, $isComplexType, $complexType, + $isSortable, + $fieldName, + $compositeFieldName, + $sortFieldName, $expected ) { $this->fieldTypeResolver->expects($this->any()) @@ -132,7 +152,30 @@ public function testGetAllAttributesTypes( ->willReturn($indexType); $this->indexTypeConverter->expects($this->any()) ->method('convert') - ->willReturn('no'); + ->with($this->anything()) + ->will($this->returnCallback( + function ($type) { + if ($type === 'no_index') { + return 'no'; + } elseif ($type === 'no_analyze') { + return 'not_analyzed'; + } + } + )); + $this->fieldNameResolver->expects($this->any()) + ->method('getFieldName') + ->with($this->anything()) + ->will($this->returnCallback( + function ($attributeMock, $context) use ($fieldName, $compositeFieldName, $sortFieldName) { + if (empty($context)) { + return $fieldName; + } elseif ($context['type'] === 'sort') { + return $sortFieldName; + } elseif ($context['type'] === 'text') { + return $compositeFieldName; + } + } + )); $productAttributeMock = $this->getMockBuilder(AbstractAttribute::class) ->setMethods(['getAttributeCode']) @@ -146,11 +189,14 @@ public function testGetAllAttributesTypes( $attributeMock = $this->getMockBuilder(AttributeAdapter::class) ->disableOriginalConstructor() - ->setMethods(['isComplexType', 'getAttributeCode']) + ->setMethods(['isComplexType', 'getAttributeCode', 'isSortable']) ->getMock(); $attributeMock->expects($this->any()) ->method('isComplexType') ->willReturn($isComplexType); + $attributeMock->expects($this->any()) + ->method('isSortable') + ->willReturn($isSortable); $attributeMock->expects($this->any()) ->method('getAttributeCode') ->willReturn($attributeCode); @@ -166,13 +212,12 @@ function ($type) use ($complexType) { static $callCount = []; $callCount[$type] = !isset($callCount[$type]) ? 1 : ++$callCount[$type]; - if ($type === 'string') { - return 'string'; - } if ($type === 'string') { return 'string'; } elseif ($type === 'float') { return 'float'; + } elseif ($type === 'keyword') { + return 'string'; } else { return $complexType; } @@ -197,6 +242,10 @@ public function attributeProvider() true, true, 'text', + false, + 'category_ids', + 'category_ids_value', + '', [ 'category_ids' => [ 'type' => 'select', @@ -217,6 +266,10 @@ public function attributeProvider() 'no', false, null, + false, + 'attr_code', + '', + '', [ 'attr_code' => [ 'type' => 'text', @@ -234,6 +287,10 @@ public function attributeProvider() null, false, null, + false, + 'attr_code', + '', + '', [ 'attr_code' => [ 'type' => 'text' @@ -243,6 +300,32 @@ public function attributeProvider() 'index' => 'no' ] ] + ], + [ + 'attr_code', + 'text', + null, + false, + null, + true, + 'attr_code', + '', + 'sort_attr_code', + [ + 'attr_code' => [ + 'type' => 'text', + 'fields' => [ + 'sort_attr_code' => [ + 'type' => 'string', + 'index' => 'not_analyzed' + ] + ] + ], + 'store_id' => [ + 'type' => 'string', + 'index' => 'no' + ] + ] ] ]; } diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php new file mode 100644 index 0000000000000..b1305e8ee601f --- /dev/null +++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php @@ -0,0 +1,212 @@ +attributeAdapterProvider = $this->getMockBuilder(AttributeProvider::class) + ->disableOriginalConstructor() + ->setMethods(['getByAttributeCode']) + ->getMock(); + $this->fieldNameResolver = $this->getMockBuilder(FieldNameResolver::class) + ->disableOriginalConstructor() + ->setMethods(['getFieldName']) + ->getMock(); + + $this->sortBuilder = (new ObjectManager($this))->getObject( + Sort::class, + [ + 'attributeAdapterProvider' => $this->attributeAdapterProvider, + 'fieldNameResolver' => $this->fieldNameResolver, + ] + ); + } + + /** + * @dataProvider getSortProvider + * @param array $sortItems + * @param $isSortable + * @param $isFloatType + * @param $isIntegerType + * @param $fieldName + * @param array $expected + */ + public function testGetSort( + array $sortItems, + $isSortable, + $isFloatType, + $isIntegerType, + $fieldName, + array $expected + ) { + /** @var MockObject|RequestInterface $request */ + $request = $this->getMockBuilder(RequestInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getSort']) + ->getMockForAbstractClass(); + $request->expects($this->any()) + ->method('getSort') + ->willReturn($sortItems); + $attributeMock = $this->getMockBuilder(AttributeAdapter::class) + ->disableOriginalConstructor() + ->setMethods(['isSortable', 'isFloatType', 'isIntegerType']) + ->getMock(); + $attributeMock->expects($this->any()) + ->method('isSortable') + ->willReturn($isSortable); + $attributeMock->expects($this->any()) + ->method('isFloatType') + ->willReturn($isFloatType); + $attributeMock->expects($this->any()) + ->method('isIntegerType') + ->willReturn($isIntegerType); + $this->attributeAdapterProvider->expects($this->any()) + ->method('getByAttributeCode') + ->with($this->anything()) + ->willReturn($attributeMock); + $this->fieldNameResolver->expects($this->any()) + ->method('getFieldName') + ->with($this->anything()) + ->will($this->returnCallback( + function ($attribute, $context) use ($fieldName) { + if (empty($context)) { + return $fieldName; + } elseif ($context['type'] === 'sort') { + return 'sort_' . $fieldName; + } + } + )); + + $this->assertEquals( + $expected, + $this->sortBuilder->getSort($request) + ); + } + + /** + * @return array + */ + public function getSortProvider() + { + return [ + [ + [ + [ + 'field' => 'entity_id', + 'direction' => 'DESC' + ] + ], + null, + null, + null, + null, + [] + ], + [ + [ + [ + 'field' => 'entity_id', + 'direction' => 'DESC' + ], + [ + 'field' => 'price', + 'direction' => 'DESC' + ], + ], + false, + false, + false, + 'price', + [ + [ + 'price' => [ + 'order' => 'desc' + ] + ] + ] + ], + [ + [ + [ + 'field' => 'entity_id', + 'direction' => 'DESC' + ], + [ + 'field' => 'price', + 'direction' => 'DESC' + ], + ], + true, + true, + true, + 'price', + [ + [ + 'price' => [ + 'order' => 'desc' + ] + ] + ] + ], + [ + [ + [ + 'field' => 'entity_id', + 'direction' => 'DESC' + ], + [ + 'field' => 'name', + 'direction' => 'DESC' + ], + ], + true, + false, + false, + 'name', + [ + [ + 'name.sort_name' => [ + 'order' => 'desc' + ] + ] + ] + ] + ]; + } +} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ResponseFactoryTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ResponseFactoryTest.php index 9ea241b2fbf5c..d89e420457206 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ResponseFactoryTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ResponseFactoryTest.php @@ -79,7 +79,7 @@ public function testCreate() 'itemTwo' => 45, ] ]; - $rawResponse = ['documents' => $documents, 'aggregations' => $aggregations]; + $rawResponse = ['documents' => $documents, 'aggregations' => $aggregations, 'total' => 2]; $exceptedResponse = [ 'documents' => [ @@ -102,6 +102,7 @@ public function testCreate() 'itemTwo' => 45 ], ], + 'total' => 2, ]; $this->documentFactory->expects($this->at(0))->method('create') @@ -118,7 +119,11 @@ public function testCreate() $this->objectManager->expects($this->once())->method('create') ->with( $this->equalTo(\Magento\Framework\Search\Response\QueryResponse::class), - $this->equalTo(['documents' => ['document1', 'document2'], 'aggregations' => 'aggregationsData']) + $this->equalTo([ + 'documents' => ['document1', 'document2'], + 'aggregations' => 'aggregationsData', + 'total' => 2 + ]) ) ->will($this->returnValue('QueryResponseObject')); diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index 6a42e4b3c9fe2..f3cd640b20f0a 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -13,6 +13,101 @@ + + + + + + quick_search_container + + + + + elasticsearchFulltextSearchCollection + + + + + + Magento\CatalogSearch\Model\ResourceModel\Fulltext\CollectionFactory + elasticsearchFulltextSearchCollectionFactory + elasticsearchFulltextSearchCollectionFactory + + + + + + catalog_view_container + + + + + elasticsearchCategoryCollection + + + + + + Magento\CatalogSearch\Model\ResourceModel\Fulltext\CollectionFactory + elasticsearchCategoryCollectionFactory + elasticsearchCategoryCollectionFactory + + + + + + advanced_search_container + + + + + elasticsearchAdvancedCollection + + + + + + elasticsearchAdvancedCollectionFactory + elasticsearchAdvancedCollectionFactory + + + + + + + Magento\Elasticsearch\Model\Advanced\ProductCollectionPrepareStrategy + Magento\Elasticsearch\Model\Advanced\ProductCollectionPrepareStrategy + + + + + + + + + + + + Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolver + + + + + Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplier + + + + + elasticsearchSearchCriteriaResolverFactory + elasticsearchSearchResultApplier\Factory + + + + + elasticsearchSearchCriteriaResolverFactory + elasticsearchSearchResultApplier\Factory + + @@ -21,7 +116,7 @@ - + Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper\CategoryFieldsProviderProxy @@ -31,7 +126,7 @@ - AdditionalFieldsForElasticsearchDataMapper + additionalFieldsProviderForElasticsearch diff --git a/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php b/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php index 6256194cef53b..ab54699c03730 100644 --- a/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php +++ b/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php @@ -35,7 +35,9 @@ public function execute(\Magento\Framework\Event\Observer $observer) { $productCollection = $observer->getEvent()->getCollection(); if ($productCollection instanceof \Magento\Framework\Data\Collection) { - $productCollection->load(); + if (!$productCollection->isLoaded()) { + $productCollection->load(); + } $this->_reviewFactory->create()->appendSummary($productCollection); } diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php index 897b67d8d46ec..f4d83ece134cf 100644 --- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php +++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php @@ -86,6 +86,7 @@ public function query(RequestInterface $request) $response = [ 'documents' => $documents, 'aggregations' => $aggregations, + 'total' => count($documents) ]; return $this->responseFactory->create($response); } diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/ResponseFactory.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/ResponseFactory.php index 776dab93c2dcd..b41a43883fde5 100644 --- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/ResponseFactory.php +++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/ResponseFactory.php @@ -68,7 +68,8 @@ public function create($rawResponse) \Magento\Framework\Search\Response\QueryResponse::class, [ 'documents' => $documents, - 'aggregations' => $aggregations + 'aggregations' => $aggregations, + 'total' => $rawResponse['total'] ] ); } diff --git a/lib/internal/Magento/Framework/Search/Request.php b/lib/internal/Magento/Framework/Search/Request.php index d55b2085e87ef..7ea3a271b4d84 100644 --- a/lib/internal/Magento/Framework/Search/Request.php +++ b/lib/internal/Magento/Framework/Search/Request.php @@ -54,6 +54,11 @@ class Request implements RequestInterface */ protected $dimensions; + /** + * @var array + */ + private $sort; + /** * @param string $name * @param string $indexName @@ -62,6 +67,7 @@ class Request implements RequestInterface * @param int|null $size * @param Dimension[] $dimensions * @param RequestBucketInterface[] $buckets + * @param array $sort */ public function __construct( $name, @@ -70,7 +76,8 @@ public function __construct( $from = null, $size = null, array $dimensions = [], - array $buckets = [] + array $buckets = [], + $sort = [] ) { $this->name = $name; $this->index = $indexName; @@ -79,6 +86,7 @@ public function __construct( $this->size = $size; $this->buckets = $buckets; $this->dimensions = $dimensions; + $this->sort = $sort; } /** @@ -136,4 +144,12 @@ public function getSize() { return $this->size; } + + /** + * {@inheritdoc} + */ + public function getSort() + { + return $this->sort; + } } diff --git a/lib/internal/Magento/Framework/Search/Request/Binder.php b/lib/internal/Magento/Framework/Search/Request/Binder.php index 34b5d72005e74..ef701c9cfc941 100644 --- a/lib/internal/Magento/Framework/Search/Request/Binder.php +++ b/lib/internal/Magento/Framework/Search/Request/Binder.php @@ -24,6 +24,9 @@ public function bind(array $requestData, array $bindData) $data['queries'] = $this->processData($requestData['queries'], $bindData['placeholder']); $data['filters'] = $this->processData($requestData['filters'], $bindData['placeholder']); $data['aggregations'] = $this->processData($requestData['aggregations'], $bindData['placeholder']); + if (isset($bindData['sort']) && isset($requestData['sort'])) { + $data['sort'] = $this->processData($requestData['sort'], $bindData['sort']); + } return $data; } diff --git a/lib/internal/Magento/Framework/Search/Request/Builder.php b/lib/internal/Magento/Framework/Search/Request/Builder.php index a16b3369cdda0..d89abcf718b6f 100644 --- a/lib/internal/Magento/Framework/Search/Request/Builder.php +++ b/lib/internal/Magento/Framework/Search/Request/Builder.php @@ -95,6 +95,16 @@ public function setFrom($from) return $this; } + /** + * @param $sort + * @return $this + */ + public function setSort($sort) + { + $this->data['sort'] = $sort; + return $this; + } + /** * Bind dimension data by name * @@ -139,6 +149,9 @@ public function create() } $data = $this->binder->bind($data, $this->data); + if (isset($this->data['sort'])) { + $data['sort'] = $this->prepareSorts($this->data['sort']); + } $data = $this->cleaner->clean($data); $this->clear(); @@ -146,6 +159,25 @@ public function create() return $this->convert($data); } + /** + * Prepare sort data for request. + * + * @param array $sorts + * @return array + */ + private function prepareSorts(array $sorts) + { + $sortData = []; + foreach ($sorts as $sortField => $direction) { + $sortData[] = [ + 'field' => $sortField, + 'direction' => $direction, + ]; + } + + return $sortData; + } + /** * Clear data * @@ -178,17 +210,21 @@ private function convert($data) 'filters' => $data['filters'] ] ); + $requestData = [ + 'name' => $data['query'], + 'indexName' => $data['index'], + 'from' => $data['from'], + 'size' => $data['size'], + 'query' => $mapper->getRootQuery(), + 'dimensions' => $this->buildDimensions(isset($data['dimensions']) ? $data['dimensions'] : []), + 'buckets' => $mapper->getBuckets() + ]; + if (isset($data['sort'])) { + $requestData['sort'] = $data['sort']; + } return $this->objectManager->create( \Magento\Framework\Search\Request::class, - [ - 'name' => $data['query'], - 'indexName' => $data['index'], - 'from' => $data['from'], - 'size' => $data['size'], - 'query' => $mapper->getRootQuery(), - 'dimensions' => $this->buildDimensions(isset($data['dimensions']) ? $data['dimensions'] : []), - 'buckets' => $mapper->getBuckets() - ] + $requestData ); } diff --git a/lib/internal/Magento/Framework/Search/RequestInterface.php b/lib/internal/Magento/Framework/Search/RequestInterface.php index 16df80f755c07..2de756e754a27 100644 --- a/lib/internal/Magento/Framework/Search/RequestInterface.php +++ b/lib/internal/Magento/Framework/Search/RequestInterface.php @@ -64,4 +64,11 @@ public function getFrom(); * @return int|null */ public function getSize(); + + /** + * Get Sort items + * + * @return array + */ + public function getSort(); } diff --git a/lib/internal/Magento/Framework/Search/Response/QueryResponse.php b/lib/internal/Magento/Framework/Search/Response/QueryResponse.php index c2328398e53dc..64ad5fd827ec1 100644 --- a/lib/internal/Magento/Framework/Search/Response/QueryResponse.php +++ b/lib/internal/Magento/Framework/Search/Response/QueryResponse.php @@ -29,14 +29,21 @@ class QueryResponse implements ResponseInterface */ protected $aggregations; + /** + * @var int + */ + private $total; + /** * @param Document[] $documents * @param AggregationInterface $aggregations + * @param int $total */ - public function __construct(array $documents, AggregationInterface $aggregations) + public function __construct(array $documents, AggregationInterface $aggregations, int $total = 0) { $this->documents = $documents; $this->aggregations = $aggregations; + $this->total = $total; } /** @@ -65,4 +72,12 @@ public function getAggregations() { return $this->aggregations; } + + /** + * {@inheritdoc} + */ + public function getTotal(): int + { + return $this->total; + } } diff --git a/lib/internal/Magento/Framework/Search/ResponseInterface.php b/lib/internal/Magento/Framework/Search/ResponseInterface.php index 3b89528532602..c6c0d0ab59e10 100644 --- a/lib/internal/Magento/Framework/Search/ResponseInterface.php +++ b/lib/internal/Magento/Framework/Search/ResponseInterface.php @@ -16,4 +16,11 @@ interface ResponseInterface extends \IteratorAggregate, \Countable * @return \Magento\Framework\Api\Search\AggregationInterface */ public function getAggregations(); + + /** + * Return total count of items. + * + * @return int + */ + public function getTotal(): int; } diff --git a/lib/internal/Magento/Framework/Search/Search.php b/lib/internal/Magento/Framework/Search/Search.php index 16dfc6fdd1ddb..cae40e1a6afba 100644 --- a/lib/internal/Magento/Framework/Search/Search.php +++ b/lib/internal/Magento/Framework/Search/Search.php @@ -68,6 +68,7 @@ public function search(SearchCriteriaInterface $searchCriteria) $this->requestBuilder->setFrom($searchCriteria->getCurrentPage() * $searchCriteria->getPageSize()); $this->requestBuilder->setSize($searchCriteria->getPageSize()); + $this->requestBuilder->setSort($searchCriteria->getSortOrders()); $request = $this->requestBuilder->create(); $searchResponse = $this->searchEngine->search($request); diff --git a/lib/internal/Magento/Framework/Search/SearchResponseBuilder.php b/lib/internal/Magento/Framework/Search/SearchResponseBuilder.php index 40fcd493861d7..3d9f7e4424935 100644 --- a/lib/internal/Magento/Framework/Search/SearchResponseBuilder.php +++ b/lib/internal/Magento/Framework/Search/SearchResponseBuilder.php @@ -46,7 +46,7 @@ public function build(ResponseInterface $response) $documents = iterator_to_array($response); $searchResult->setItems($documents); $searchResult->setAggregations($response->getAggregations()); - $searchResult->setTotalCount(count($documents)); + $searchResult->setTotalCount($response->getTotal()); return $searchResult; } diff --git a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php index 55d26493ca379..a35e1fd8b6151 100644 --- a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php +++ b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php @@ -155,6 +155,7 @@ public function testQuery() 'aggregation2' => [2, 4], ], ], + 'total' => 1 ]; $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class) diff --git a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/ResponseFactoryTest.php b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/ResponseFactoryTest.php index c0699156decd0..3d21064b13d47 100644 --- a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/ResponseFactoryTest.php +++ b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/ResponseFactoryTest.php @@ -49,6 +49,7 @@ public function testCreate() ['title' => 'twoTitle', 'description' => 'twoDescription'], ], 'aggregations' => [], + 'total' => 2 ]; $this->documentFactory->expects($this->at(0))->method('create') @@ -61,7 +62,7 @@ public function testCreate() $this->objectManager->expects($this->once())->method('create') ->with( $this->equalTo(\Magento\Framework\Search\Response\QueryResponse::class), - $this->equalTo(['documents' => ['document1', 'document2'], 'aggregations' => null]) + $this->equalTo(['documents' => ['document1', 'document2'], 'aggregations' => null, 'total' => 2]) ) ->will($this->returnValue('QueryResponseObject')); diff --git a/lib/internal/Magento/Framework/Search/Test/Unit/Response/QueryResponseTest.php b/lib/internal/Magento/Framework/Search/Test/Unit/Response/QueryResponseTest.php index a60a280cb602a..a994559f28f0b 100644 --- a/lib/internal/Magento/Framework/Search/Test/Unit/Response/QueryResponseTest.php +++ b/lib/internal/Magento/Framework/Search/Test/Unit/Response/QueryResponseTest.php @@ -46,6 +46,7 @@ protected function setUp() [ 'documents' => $this->documents, 'aggregations' => $this->aggregations, + 'total' => 1 ] ); } diff --git a/lib/internal/Magento/Framework/Search/Test/Unit/SearchResponseBuilderTest.php b/lib/internal/Magento/Framework/Search/Test/Unit/SearchResponseBuilderTest.php index a0b11959a42db..9ffafae9622f3 100644 --- a/lib/internal/Magento/Framework/Search/Test/Unit/SearchResponseBuilderTest.php +++ b/lib/internal/Magento/Framework/Search/Test/Unit/SearchResponseBuilderTest.php @@ -67,7 +67,7 @@ public function testBuild() /** @var QueryResponse|\PHPUnit_Framework_MockObject_MockObject $response */ $response = $this->getMockBuilder(\Magento\Framework\Search\Response\QueryResponse::class) - ->setMethods(['getIterator', 'getAggregations']) + ->setMethods(['getIterator', 'getAggregations', 'getTotal']) ->disableOriginalConstructor() ->getMockForAbstractClass(); $response->expects($this->any()) @@ -76,6 +76,9 @@ public function testBuild() $response->expects($this->once()) ->method('getAggregations') ->willReturn($aggregations); + $response->expects($this->any()) + ->method('getTotal') + ->willReturn(1); $result = $this->model->build($response); From 764612e55df56d72f326246c39358a35d3fb07cb Mon Sep 17 00:00:00 2001 From: Nick de Kleijn Date: Tue, 15 Jan 2019 16:39:08 +0100 Subject: [PATCH 005/108] #20310 change product_price_value based on tax setting --- .../Magento/Tax/Plugin/Checkout/CustomerData/Cart.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php b/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php index 87f65ef311ac2..c1b495d4ecf98 100644 --- a/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php +++ b/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php @@ -68,6 +68,14 @@ public function afterGetSectionData(\Magento\Checkout\CustomerData\Cart $subject $this->itemPriceRenderer->setItem($item); $this->itemPriceRenderer->setTemplate('checkout/cart/item/price/sidebar.phtml'); $result['items'][$key]['product_price']=$this->itemPriceRenderer->toHtml(); + if($this->itemPriceRenderer->displayPriceExclTax()) { + $result['items'][$key]['product_price_value'] = $item->getCalculationPrice(); + } elseif ($this->itemPriceRenderer->displayPriceInclTax()) { + $result['items'][$key]['product_price_value'] = $item->getPriceInclTax(); + } elseif ($this->itemPriceRenderer->displayBothPrices()) { + $result['items'][$key]['product_price_value']['incl_tax'] = $item->getPriceInclTax(); + $result['items'][$key]['product_price_value']['excl_tax'] = $item->getCalculationPrice(); + } } } } From 8233da21ed4323d98b3445e2864cc758be02e7fb Mon Sep 17 00:00:00 2001 From: Christian Walter Date: Tue, 22 Jan 2019 11:08:17 +0100 Subject: [PATCH 006/108] [TASK] Fix translation of attribute store label in getAdditionalData Attribute labels should be translated in magento admin setting correct translation for stores --- app/code/Magento/Catalog/Block/Product/View/Attributes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Block/Product/View/Attributes.php b/app/code/Magento/Catalog/Block/Product/View/Attributes.php index cb59d86a74512..b096874081eb9 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Attributes.php +++ b/app/code/Magento/Catalog/Block/Product/View/Attributes.php @@ -90,7 +90,7 @@ public function getAdditionalData(array $excludeAttr = []) if (is_string($value) && strlen($value)) { $data[$attribute->getAttributeCode()] = [ - 'label' => __($attribute->getStoreLabel()), + 'label' => $attribute->getStoreLabel(), 'value' => $value, 'code' => $attribute->getAttributeCode(), ]; From 777f53b3a524b71a0744777e151e818fd99f6fff Mon Sep 17 00:00:00 2001 From: Vitaliy Honcharenko Date: Tue, 15 Jan 2019 15:02:30 +0200 Subject: [PATCH 007/108] MAGETWO-95294: Mysql search slow on the catalog page - fix tests --- .../Catalog/Block/Product/ListProduct.php | 21 ++- .../Model/AddStockStatusToCollection.php | 21 ++- .../CatalogInventory/Model/Plugin/Layer.php | 29 +++- .../Model/AddStockStatusToCollectionTest.php | 16 ++ .../Test/Unit/Model/Plugin/LayerTest.php | 22 ++- .../Magento/CatalogInventory/composer.json | 1 + .../Controller/Advanced/Result.php | 12 +- ...oductCollectionPrepareStrategyProvider.php | 2 + .../ResourceModel/Advanced/Collection.php | 126 +++++++++++++-- .../ResourceModel/Fulltext/Collection.php | 144 +++++++++++++----- .../Collection/SearchCriteriaResolver.php | 2 +- .../SearchCriteriaResolverInterface.php | 2 + .../Collection/SearchResultApplier.php | 18 ++- .../SearchResultApplierInterface.php | 2 + .../Collection/TotalRecordsResolver.php | 21 +++ .../TotalRecordsResolverInterface.php | 20 +++ .../Model/Search/ItemCollectionProvider.php | 2 +- .../ItemCollectionProviderInterface.php | 2 + .../Unit/Controller/Advanced/ResultTest.php | 31 ++-- .../ResourceModel/Advanced/CollectionTest.php | 16 +- .../ResourceModel/Fulltext/CollectionTest.php | 17 ++- app/code/Magento/CatalogSearch/etc/di.xml | 1 + .../frontend/templates/advanced/result.phtml | 2 +- .../SearchAdapter/Query/Builder.php | 2 + .../Product/FieldProvider/StaticField.php | 11 +- .../Layer/Category/ItemCollectionProvider.php | 53 +++++++ .../Layer/Search/ItemCollectionProvider.php | 7 +- .../ResourceModel/Advanced/Collection.php | 51 ------- .../ResourceModel/Fulltext/Collection.php | 54 ------- .../Collection/SearchCriteriaResolver.php | 2 +- .../Collection/SearchResultApplier.php | 5 +- .../Collection/TotalRecordsResolver.php | 38 +++++ .../Elasticsearch/SearchAdapter/Adapter.php | 3 +- .../SearchAdapter/Query/Builder.php | 2 + .../SearchAdapter/Query/Builder/Sort.php | 10 +- .../SearchAdapter/Query/Builder/SortTest.php | 1 + app/code/Magento/Elasticsearch/etc/di.xml | 39 +++-- ...kProductCollectionBeforeToHtmlObserver.php | 3 + .../Framework/Search/RequestConfigTest.php | 3 +- .../Magento/Framework/Search/Request.php | 16 +- .../Framework/Search/Request/Binder.php | 4 + .../Framework/Search/Request/Builder.php | 9 +- .../Search/Response/QueryResponse.php | 7 +- .../Magento/Framework/Search/Search.php | 5 +- .../Search/SearchResponseBuilder.php | 5 + .../Magento/Framework/Search/etc/requests.xsd | 4 +- 46 files changed, 603 insertions(+), 261 deletions(-) create mode 100644 app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php create mode 100644 app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolverInterface.php create mode 100644 app/code/Magento/Elasticsearch/Model/Layer/Category/ItemCollectionProvider.php delete mode 100644 app/code/Magento/Elasticsearch/Model/ResourceModel/Advanced/Collection.php delete mode 100644 app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection.php create mode 100644 app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php diff --git a/app/code/Magento/Catalog/Block/Product/ListProduct.php b/app/code/Magento/Catalog/Block/Product/ListProduct.php index 0126d546d0ba2..c1d79894162ae 100644 --- a/app/code/Magento/Catalog/Block/Product/ListProduct.php +++ b/app/code/Magento/Catalog/Block/Product/ListProduct.php @@ -178,8 +178,9 @@ private function getDefaultListingMode() } /** - * Need use as _prepareLayout - but problem in declaring collection from - * another block (was problem with search result) + * Need use as _prepareLayout - but problem in declaring collection from another block. + * (was problem with search result) + * * @return $this */ protected function _beforeToHtml() @@ -188,7 +189,7 @@ protected function _beforeToHtml() $this->addToolbarBlock($collection); - if (!$collection->isLoaded()){ + if (!$collection->isLoaded()) { $collection->load(); } @@ -264,6 +265,8 @@ public function getToolbarHtml() } /** + * Set collection. + * * @param AbstractCollection $collection * @return $this */ @@ -274,7 +277,9 @@ public function setCollection($collection) } /** - * @param array|string|integer| Element $code + * Add attribute. + * + * @param array|string|integer|Element $code * @return $this */ public function addAttribute($code) @@ -284,6 +289,8 @@ public function addAttribute($code) } /** + * Get price block template. + * * @return mixed */ public function getPriceBlockTemplate() @@ -373,6 +380,8 @@ public function getAddToCartPostParams(Product $product) } /** + * Get product price. + * * @param Product $product * @return string */ @@ -398,8 +407,8 @@ public function getProductPrice(Product $product) } /** - * Specifies that price rendering should be done for the list of products - * i.e. rendering happens in the scope of product list, but not single product + * Specifies that price rendering should be done for the list of products. + * (rendering happens in the scope of product list, but not single product) * * @return Render */ diff --git a/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php b/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php index 2c52b49c1a039..0a02d4eb6a9a6 100644 --- a/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php +++ b/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php @@ -7,6 +7,8 @@ namespace Magento\CatalogInventory\Model; use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Framework\Search\EngineResolverInterface; +use Magento\Search\Model\EngineResolver; /** * Catalog inventory module plugin @@ -17,18 +19,27 @@ class AddStockStatusToCollection * @var \Magento\CatalogInventory\Helper\Stock */ protected $stockHelper; - + + /** + * @var EngineResolverInterface + */ + private $engineResolver; + /** - * @param \Magento\CatalogInventory\Model\Configuration $configuration * @param \Magento\CatalogInventory\Helper\Stock $stockHelper + * @param EngineResolverInterface $engineResolver */ public function __construct( - \Magento\CatalogInventory\Helper\Stock $stockHelper + \Magento\CatalogInventory\Helper\Stock $stockHelper, + EngineResolverInterface $engineResolver ) { $this->stockHelper = $stockHelper; + $this->engineResolver = $engineResolver; } /** + * Add stock filter to collection. + * * @param Collection $productCollection * @param bool $printQuery * @param bool $logQuery @@ -36,7 +47,9 @@ public function __construct( */ public function beforeLoad(Collection $productCollection, $printQuery = false, $logQuery = false) { - $this->stockHelper->addIsInStockFilterToCollection($productCollection); + if ($this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE) { + $this->stockHelper->addIsInStockFilterToCollection($productCollection); + } return [$printQuery, $logQuery]; } } diff --git a/app/code/Magento/CatalogInventory/Model/Plugin/Layer.php b/app/code/Magento/CatalogInventory/Model/Plugin/Layer.php index b8e8e47bb1fb0..168e947b8fa57 100644 --- a/app/code/Magento/CatalogInventory/Model/Plugin/Layer.php +++ b/app/code/Magento/CatalogInventory/Model/Plugin/Layer.php @@ -3,8 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\CatalogInventory\Model\Plugin; +use Magento\Framework\Search\EngineResolverInterface; +use Magento\Search\Model\EngineResolver; + +/** + * Catalog inventory plugin for layer. + */ class Layer { /** @@ -21,16 +28,24 @@ class Layer */ protected $scopeConfig; + /** + * @var EngineResolverInterface + */ + private $engineResolver; + /** * @param \Magento\CatalogInventory\Helper\Stock $stockHelper * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param EngineResolverInterface $engineResolver */ public function __construct( \Magento\CatalogInventory\Helper\Stock $stockHelper, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + EngineResolverInterface $engineResolver ) { $this->stockHelper = $stockHelper; $this->scopeConfig = $scopeConfig; + $this->engineResolver = $engineResolver; } /** @@ -46,12 +61,22 @@ public function beforePrepareProductCollection( \Magento\Catalog\Model\Layer $subject, \Magento\Catalog\Model\ResourceModel\Collection\AbstractCollection $collection ) { - if ($this->_isEnabledShowOutOfStock()) { + if (!$this->isCurrentEngineMysql() || $this->_isEnabledShowOutOfStock()) { return; } $this->stockHelper->addIsInStockFilterToCollection($collection); } + /** + * Check if current engine is MYSQL. + * + * @return bool + */ + private function isCurrentEngineMysql() + { + return $this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE; + } + /** * Get config value for 'display out of stock' option * diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/AddStockStatusToCollectionTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/AddStockStatusToCollectionTest.php index af35666ced3e5..906df54732775 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/AddStockStatusToCollectionTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/AddStockStatusToCollectionTest.php @@ -6,6 +6,7 @@ namespace Magento\CatalogInventory\Test\Unit\Model; use Magento\CatalogInventory\Model\AddStockStatusToCollection; +use Magento\Framework\Search\EngineResolverInterface; class AddStockStatusToCollectionTest extends \PHPUnit\Framework\TestCase { @@ -19,13 +20,24 @@ class AddStockStatusToCollectionTest extends \PHPUnit\Framework\TestCase */ protected $stockHelper; + /** + * @var EngineResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $engineResolver; + protected function setUp() { $this->stockHelper = $this->createMock(\Magento\CatalogInventory\Helper\Stock::class); + $this->engineResolver = $this->getMockBuilder(EngineResolverInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getCurrentSearchEngine']) + ->getMockForAbstractClass(); + $this->plugin = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( \Magento\CatalogInventory\Model\AddStockStatusToCollection::class, [ 'stockHelper' => $this->stockHelper, + 'engineResolver' => $this->engineResolver ] ); } @@ -36,6 +48,10 @@ public function testAddStockStatusToCollection() ->disableOriginalConstructor() ->getMock(); + $this->engineResolver->expects($this->any()) + ->method('getCurrentSearchEngine') + ->willReturn('mysql'); + $this->stockHelper->expects($this->once()) ->method('addIsInStockFilterToCollection') ->with($productCollection) diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/LayerTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/LayerTest.php index 287459bd8cbc8..b64563a35176d 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/LayerTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/LayerTest.php @@ -5,6 +5,8 @@ */ namespace Magento\CatalogInventory\Test\Unit\Model\Plugin; +use Magento\Framework\Search\EngineResolverInterface; + class LayerTest extends \PHPUnit\Framework\TestCase { /** @@ -22,14 +24,24 @@ class LayerTest extends \PHPUnit\Framework\TestCase */ protected $_stockHelperMock; + /** + * @var EngineResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $engineResolver; + protected function setUp() { $this->_scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); $this->_stockHelperMock = $this->createMock(\Magento\CatalogInventory\Helper\Stock::class); + $this->engineResolver = $this->getMockBuilder(EngineResolverInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getCurrentSearchEngine']) + ->getMockForAbstractClass(); $this->_model = new \Magento\CatalogInventory\Model\Plugin\Layer( $this->_stockHelperMock, - $this->_scopeConfigMock + $this->_scopeConfigMock, + $this->engineResolver ); } @@ -38,6 +50,10 @@ protected function setUp() */ public function testAddStockStatusDisabledShow() { + $this->engineResolver->expects($this->any()) + ->method('getCurrentSearchEngine') + ->willReturn('mysql'); + $this->_scopeConfigMock->expects( $this->once() )->method( @@ -60,6 +76,10 @@ public function testAddStockStatusDisabledShow() */ public function testAddStockStatusEnabledShow() { + $this->engineResolver->expects($this->any()) + ->method('getCurrentSearchEngine') + ->willReturn('mysql'); + $this->_scopeConfigMock->expects( $this->once() )->method( diff --git a/app/code/Magento/CatalogInventory/composer.json b/app/code/Magento/CatalogInventory/composer.json index 007d744b2296f..eb6239ea87ef0 100644 --- a/app/code/Magento/CatalogInventory/composer.json +++ b/app/code/Magento/CatalogInventory/composer.json @@ -8,6 +8,7 @@ "php": "~7.1.3||~7.2.0", "magento/framework": "*", "magento/module-catalog": "*", + "magento/module-search": "*", "magento/module-config": "*", "magento/module-customer": "*", "magento/module-eav": "*", diff --git a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php index 184fd9cfd5b37..384132415a10e 100644 --- a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php +++ b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php @@ -60,15 +60,9 @@ public function execute() { try { $this->_catalogSearchAdvanced->addFilters($this->getRequest()->getQueryValue()); - $size = $this->_catalogSearchAdvanced->getProductCollection()->getSize(); - - $handles = null; - if ($size == 0) { - $this->_view->getPage()->initLayout(); - $handles = $this->_view->getLayout()->getUpdate()->getHandles(); - $handles[] = static::DEFAULT_NO_RESULT_HANDLE; - } - + $this->_view->getPage()->initLayout(); + $handles = $this->_view->getLayout()->getUpdate()->getHandles(); + $handles[] = static::DEFAULT_NO_RESULT_HANDLE; $this->_view->loadLayout($handles); $this->_view->renderLayout(); } catch (\Magento\Framework\Exception\LocalizedException $e) { diff --git a/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyProvider.php b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyProvider.php index a00db6f517939..6e963ea1aa8ac 100644 --- a/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyProvider.php @@ -35,6 +35,8 @@ public function __construct( } /** + * Get strategy provider for product collection prepare process. + * * @return ProductCollectionPrepareStrategyInterface */ public function getStrategy(): ProductCollectionPrepareStrategyInterface diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php index 80d5a1d61798f..2d7ea3b6ec861 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php @@ -6,8 +6,14 @@ namespace Magento\CatalogSearch\Model\ResourceModel\Advanced; use Magento\Catalog\Model\Product; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; +use Magento\Framework\Search\EngineResolverInterface; +use Magento\Search\Model\EngineResolver; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory; use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\DB\Select; use Magento\Framework\Api\Search\SearchCriteriaBuilder; use Magento\Framework\Api\Search\SearchResultFactory; use Magento\Framework\EntityManager\MetadataPool; @@ -16,7 +22,6 @@ use Magento\Framework\Search\Request\NonExistingRequestNameException; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; -use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolver; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; use Magento\Framework\App\ObjectManager; use Magento\Framework\Api\Search\SearchResultInterface; @@ -84,6 +89,21 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection */ private $searchResultApplierFactory; + /** + * @var TotalRecordsResolverFactory + */ + private $totalRecordsResolverFactory; + + /** + * @var EngineResolverInterface + */ + private $engineResolver; + + /** + * @var array + */ + private $searchOrders; + /** * Collection constructor * @@ -116,6 +136,8 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param string $searchRequestName * @param SearchCriteriaResolverFactory|null $searchCriteriaResolverFactory * @param SearchResultApplierFactory|null $searchResultApplierFactory + * @param TotalRecordsResolverFactory|null $totalRecordsResolverFactory + * @param EngineResolverInterface|null $engineResolver * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -147,7 +169,9 @@ public function __construct( MetadataPool $metadataPool = null, $searchRequestName = 'advanced_search_container', SearchCriteriaResolverFactory $searchCriteriaResolverFactory = null, - SearchResultApplierFactory $searchResultApplierFactory = null + SearchResultApplierFactory $searchResultApplierFactory = null, + TotalRecordsResolverFactory $totalRecordsResolverFactory = null, + EngineResolverInterface $engineResolver = null ) { $this->requestBuilder = $requestBuilder; $this->searchEngine = $searchEngine; @@ -161,6 +185,10 @@ public function __construct( ->get(SearchCriteriaResolverFactory::class); $this->searchResultApplierFactory = $searchResultApplierFactory ?: ObjectManager::getInstance() ->get(SearchResultApplierFactory::class); + $this->totalRecordsResolverFactory = $totalRecordsResolverFactory ?: ObjectManager::getInstance() + ->get(TotalRecordsResolverFactory::class); + $this->engineResolver = $engineResolver ?: ObjectManager::getInstance() + ->get(EngineResolverInterface::class); parent::__construct( $entityFactory, $logger, @@ -202,6 +230,73 @@ public function addFieldsToFilter($fields) return $this; } + /** + * @inheritdoc + */ + public function setOrder($attribute, $dir = Select::SQL_DESC) + { + $this->setSearchOrder($attribute, $dir); + if ($this->isCurrentEngineMysql()) { + parent::setOrder($attribute, $dir); + } + + return $this; + } + + /** + * @inheritdoc + */ + public function addCategoryFilter(\Magento\Catalog\Model\Category $category) + { + if ($this->isCurrentEngineMysql()) { + parent::addCategoryFilter($category); + } else { + $this->addFieldToFilter('category_ids', $category->getId()); + $this->_productLimitationPrice(); + } + + return $this; + } + + /** + * @inheritdoc + */ + public function setVisibility($visibility) + { + if ($this->isCurrentEngineMysql()) { + parent::setVisibility($visibility); + } else { + $this->addFieldToFilter('visibility', $visibility); + } + + return $this; + } + + /** + * Set sort order for search query. + * + * @param string $field + * @param string $direction + * @return void + */ + private function setSearchOrder($field, $direction) + { + $field = (string)$this->_getMappedField($field); + $direction = strtoupper($direction) == self::SORT_ORDER_ASC ? self::SORT_ORDER_ASC : self::SORT_ORDER_DESC; + + $this->searchOrders[$field] = $direction; + } + + /** + * Check if current engine is MYSQL. + * + * @return bool + */ + private function isCurrentEngineMysql() + { + return $this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE; + } + /** * @inheritdoc */ @@ -220,7 +315,7 @@ protected function _renderFiltersBefore() $searchCriteria = $this->getSearchCriteriaResolver()->resolve(); try { $this->searchResult = $this->getSearch()->search($searchCriteria); - $this->_totalRecords = $this->searchResult->getTotalCount(); + $this->_totalRecords = $this->getTotalRecordsResolver($this->searchResult)->resolve(); } catch (EmptyRequestDataException $e) { /** @var \Magento\Framework\Api\Search\SearchResultInterface $searchResult */ $this->searchResult = $this->searchResultFactory->create()->setItems([]); @@ -236,36 +331,47 @@ protected function _renderFiltersBefore() } /** - * Clean order data. + * Get total records resolver. + * + * @param SearchResultInterface $searchResult + * @return TotalRecordsResolverInterface */ - public function cleanOrder() + private function getTotalRecordsResolver(SearchResultInterface $searchResult): TotalRecordsResolverInterface { - $this->_orders = []; + return $this->totalRecordsResolverFactory->create([ + 'searchResult' => $searchResult, + ]); } /** - * @return SearchCriteriaResolver + * Get search criteria resolver. + * + * @return SearchCriteriaResolverInterface */ - private function getSearchCriteriaResolver() + private function getSearchCriteriaResolver(): SearchCriteriaResolverInterface { return $this->searchCriteriaResolverFactory->create([ 'builder' => $this->getSearchCriteriaBuilder(), 'collection' => $this, 'searchRequestName' => $this->searchRequestName, + 'currentPage' => $this->_curPage, 'size' => $this->getPageSize(), - 'orders' => $this->_orders, + 'orders' => $this->searchOrders, ]); } /** + * Get search result applier. + * * @param SearchResultInterface $searchResult * @return SearchResultApplierInterface */ - private function getSearchResultApplier(SearchResultInterface $searchResult) + private function getSearchResultApplier(SearchResultInterface $searchResult): SearchResultApplierInterface { return $this->searchResultApplierFactory->create([ 'collection' => $this, 'searchResult' => $searchResult, + 'orders' => $this->_orders ]); } diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index 0b935638e93a7..7a5cb1ebb3a0e 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -6,20 +6,19 @@ namespace Magento\CatalogSearch\Model\ResourceModel\Fulltext; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; -use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolver; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; -use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplier; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; +use Magento\Framework\Search\EngineResolverInterface; use Magento\Framework\Data\Collection\Db\SizeResolverInterfaceFactory; +use Magento\Framework\DB\Select; use Magento\Framework\Api\Search\SearchResultInterface; -use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; -use Magento\Framework\Indexer\DimensionFactory; use Magento\CatalogSearch\Model\Search\RequestGenerator; -use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\StateException; -use Magento\Framework\Search\Adapter\Mysql\TemporaryStorage; use Magento\Framework\Search\Response\QueryResponse; use Magento\Framework\Search\Request\EmptyRequestDataException; use Magento\Framework\Search\Request\NonExistingRequestNameException; @@ -27,6 +26,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\App\ObjectManager; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; +use Magento\Search\Model\EngineResolver; /** * Fulltext Collection @@ -36,6 +36,7 @@ * @api * @since 100.0.2 * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyFields) */ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection { @@ -116,6 +117,21 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection */ private $searchResultApplierFactory; + /** + * @var TotalRecordsResolverFactory + */ + private $totalRecordsResolverFactory; + + /** + * @var EngineResolverInterface + */ + private $engineResolver; + + /** + * @var array + */ + private $searchOrders; + /** * Collection constructor * @@ -150,12 +166,12 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param \Magento\Search\Api\SearchInterface|null $search * @param \Magento\Framework\Api\Search\SearchCriteriaBuilder|null $searchCriteriaBuilder * @param \Magento\Framework\Api\FilterBuilder|null $filterBuilder - * @param TableMaintainer|null $tableMaintainer - * @param PriceTableResolver|null $priceTableResolver - * @param DimensionFactory|null $dimensionFactory * @param SearchCriteriaResolverFactory|null $searchCriteriaResolverFactory * @param SearchResultApplierFactory|null $searchResultApplierFactory + * @param TotalRecordsResolverFactory|null $totalRecordsResolverFactory + * @param EngineResolverInterface|null $engineResolver * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function __construct( \Magento\Framework\Data\Collection\EntityFactory $entityFactory, @@ -190,7 +206,9 @@ public function __construct( \Magento\Framework\Api\Search\SearchCriteriaBuilder $searchCriteriaBuilder = null, \Magento\Framework\Api\FilterBuilder $filterBuilder = null, SearchCriteriaResolverFactory $searchCriteriaResolverFactory = null, - SearchResultApplierFactory $searchResultApplierFactory = null + SearchResultApplierFactory $searchResultApplierFactory = null, + TotalRecordsResolverFactory $totalRecordsResolverFactory = null, + EngineResolverInterface $engineResolver = null ) { $this->queryFactory = $catalogSearchData; if ($searchResultFactory === null) { @@ -234,6 +252,10 @@ public function __construct( ->get(SearchCriteriaResolverFactory::class); $this->searchResultApplierFactory = $searchResultApplierFactory ?: ObjectManager::getInstance() ->get(SearchResultApplierFactory::class); + $this->totalRecordsResolverFactory = $totalRecordsResolverFactory ?: ObjectManager::getInstance() + ->get(TotalRecordsResolverFactory::class); + $this->engineResolver = $engineResolver ?: ObjectManager::getInstance() + ->get(EngineResolverInterface::class); } /** @@ -364,6 +386,19 @@ public function addSearchFilter($query) return $this; } + /** + * @inheritdoc + */ + public function setOrder($attribute, $dir = Select::SQL_DESC) + { + $this->setSearchOrder($attribute, $dir); + if ($this->isCurrentEngineMysql()) { + parent::setOrder($attribute, $dir); + } + + return $this; + } + /** * @inheritdoc */ @@ -379,7 +414,7 @@ protected function _renderFiltersBefore() $searchCriteria = $this->getSearchCriteriaResolver()->resolve(); try { $this->searchResult = $this->getSearch()->search($searchCriteria); - $this->_totalRecords = $this->searchResult->getTotalCount(); + $this->_totalRecords = $this->getTotalRecordsResolver($this->searchResult)->resolve(); } catch (EmptyRequestDataException $e) { /** @var \Magento\Framework\Api\Search\SearchResultInterface $searchResult */ $this->searchResult = $this->searchResultFactory->create()->setItems([]); @@ -389,22 +424,55 @@ protected function _renderFiltersBefore() } $this->getSearchResultApplier($this->searchResult)->apply(); - parent::_renderFiltersBefore(); + + $this->_eventManager->dispatch('catalog_search_collection_render_filters_before', ['collection' => $this]); + } + + /** + * Set sort order for search query. + * + * @param string $field + * @param string $direction + * @return void + */ + private function setSearchOrder($field, $direction) + { + $field = (string)$this->_getMappedField($field); + $direction = strtoupper($direction) == self::SORT_ORDER_ASC ? self::SORT_ORDER_ASC : self::SORT_ORDER_DESC; + + $this->searchOrders[$field] = $direction; } /** - * Clean order data. + * Check if current engine is MYSQL. + * + * @return bool + */ + private function isCurrentEngineMysql() + { + return $this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE; + } + + /** + * Get total records resolver. + * + * @param SearchResultInterface $searchResult + * @return TotalRecordsResolverInterface */ - public function cleanOrder() + private function getTotalRecordsResolver(SearchResultInterface $searchResult): TotalRecordsResolverInterface { - $this->_orders = []; + return $this->totalRecordsResolverFactory->create([ + 'searchResult' => $searchResult, + ]); } /** - * @return SearchCriteriaResolver + * Get search criteria resolver. + * + * @return SearchCriteriaResolverInterface */ - private function getSearchCriteriaResolver() + private function getSearchCriteriaResolver(): SearchCriteriaResolverInterface { return $this->searchCriteriaResolverFactory->create([ 'builder' => $this->getSearchCriteriaBuilder(), @@ -412,19 +480,22 @@ private function getSearchCriteriaResolver() 'searchRequestName' => $this->searchRequestName, 'currentPage' => $this->_curPage, 'size' => $this->getPageSize(), - 'orders' => $this->_orders, + 'orders' => $this->searchOrders, ]); } /** + * Get search result applier. + * * @param SearchResultInterface $searchResult - * @return SearchResultApplier + * @return SearchResultApplierInterface */ - private function getSearchResultApplier(SearchResultInterface $searchResult) + private function getSearchResultApplier(SearchResultInterface $searchResult): SearchResultApplierInterface { return $this->searchResultApplierFactory->create([ 'collection' => $this, 'searchResult' => $searchResult, + 'orders' => $this->_orders, ]); } @@ -453,24 +524,6 @@ protected function _renderFilters() return parent::_renderFilters(); } - /** - * Set Order field - * - * @param string $attribute - * @param string $dir - * @return $this - */ - public function setOrder($attribute, $dir = Select::SQL_DESC) - { - if ($attribute === 'relevance') { - $attribute = TemporaryStorage::FIELD_SCORE; - } - - parent::setOrder($attribute, $dir); - - return $this; - } - /** * Stub method for compatibility with other search engines * @@ -517,7 +570,12 @@ public function getFacetedData($field) public function addCategoryFilter(\Magento\Catalog\Model\Category $category) { $this->addFieldToFilter('category_ids', $category->getId()); - return parent::addCategoryFilter($category); + if ($this->isCurrentEngineMysql()) { + parent::addCategoryFilter($category); + } else { + $this->_productLimitationPrice(); + } + return $this; } /** @@ -529,7 +587,11 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) public function setVisibility($visibility) { $this->addFieldToFilter('visibility', $visibility); - return parent::setVisibility($visibility); + if ($this->isCurrentEngineMysql()) { + parent::setVisibility($visibility); + } + + return $this; } /** diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php index 0c93ecb2627c7..632e1ab9270df 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php @@ -39,7 +39,7 @@ public function __construct( } /** - * @return SearchCriteria + * @inheritdoc */ public function resolve() : SearchCriteria { diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverInterface.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverInterface.php index 2ca6f6b617f8c..047fa7f71e400 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverInterface.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverInterface.php @@ -14,6 +14,8 @@ interface SearchCriteriaResolverInterface { /** + * Resolve specific attribute. + * * @return SearchCriteria */ public function resolve(): SearchCriteria; diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php index b7c8ed092bad3..20c237646e524 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php @@ -31,23 +31,31 @@ class SearchResultApplier implements SearchResultApplierInterface */ private $temporaryStorageFactory; + /** + * @var array + */ + private $orders; + /** * @param Collection $collection * @param SearchResultInterface $searchResult * @param TemporaryStorageFactory $temporaryStorageFactory + * @param array $orders */ public function __construct( Collection $collection, SearchResultInterface $searchResult, - TemporaryStorageFactory $temporaryStorageFactory + TemporaryStorageFactory $temporaryStorageFactory, + array $orders ) { $this->collection = $collection; $this->searchResult = $searchResult; $this->temporaryStorageFactory = $temporaryStorageFactory; + $this->orders = $orders; } /** - * @return void + * @inheritdoc */ public function apply() { @@ -61,5 +69,11 @@ public function apply() 'e.entity_id = search_result.' . TemporaryStorage::FIELD_ENTITY_ID, [] ); + + if (isset($this->orders['relevance'])) { + $this->collection->getSelect()->order( + 'search_result.' . TemporaryStorage::FIELD_SCORE . ' ' . $this->orders['relevance'] + ); + } } } diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplierInterface.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplierInterface.php index 111b6097632d3..1b3e2a6bbac71 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplierInterface.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplierInterface.php @@ -12,6 +12,8 @@ interface SearchResultApplierInterface { /** + * Apply search results to collection. + * * @return void */ public function apply(); diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php new file mode 100644 index 0000000000000..713035ff43db7 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php @@ -0,0 +1,21 @@ +createPartialMock(\Magento\Framework\App\View::class, ['loadLayout', 'renderLayout']); + $view = $this->createPartialMock( + \Magento\Framework\App\View::class, + ['loadLayout', 'renderLayout', 'getPage', 'getLayout'] + ); + $update = $this->createPartialMock(\Magento\Framework\View\Model\Layout\Merge::class, ['getHandles']); + $update->expects($this->once())->method('getHandles')->will($this->returnValue([])); + $layout = $this->createPartialMock(\Magento\Framework\View\Result\Layout::class, ['getUpdate']); + $layout->expects($this->once())->method('getUpdate')->will($this->returnValue($update)); + $view->expects($this->once())->method('getLayout')->will($this->returnValue($layout)); + $page = $this->createPartialMock(\Magento\Framework\View\Result\Page::class, ['initLayout']); + $view->expects($this->once())->method('getPage')->will($this->returnValue($page)); $view->expects($this->once())->method('loadLayout')->will( $this->returnCallback( function () use (&$filters, $expectedQuery) { @@ -32,15 +42,9 @@ function () use (&$filters, $expectedQuery) { $request = $this->createPartialMock(\Magento\Framework\App\Console\Request::class, ['getQueryValue']); $request->expects($this->once())->method('getQueryValue')->will($this->returnValue($expectedQuery)); - $collection = $this->createPartialMock( - \Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection::class, - ['getSize'] - ); - $collection->expects($this->once())->method('getSize')->will($this->returnValue(1)); - $catalogSearchAdvanced = $this->createPartialMock( \Magento\CatalogSearch\Model\Advanced::class, - ['addFilters', '__wakeup', 'getProductCollection'] + ['addFilters', '__wakeup'] ); $catalogSearchAdvanced->expects($this->once())->method('addFilters')->will( $this->returnCallback( @@ -49,8 +53,6 @@ function ($added) use (&$filters) { } ) ); - $catalogSearchAdvanced->expects($this->once())->method('getProductCollection') - ->will($this->returnValue($collection)); $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $context = $objectManager->getObject( @@ -189,20 +191,11 @@ public function testNoResultsHandle() $request = $this->createPartialMock(\Magento\Framework\App\Console\Request::class, ['getQueryValue']); $request->expects($this->once())->method('getQueryValue')->will($this->returnValue($expectedQuery)); - $collection = $this->createPartialMock( - \Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection::class, - ['getSize'] - ); - $collection->expects($this->once())->method('getSize')->will($this->returnValue(0)); - $catalogSearchAdvanced = $this->createPartialMock( \Magento\CatalogSearch\Model\Advanced::class, ['addFilters', '__wakeup', 'getProductCollection'] ); - $catalogSearchAdvanced->expects($this->once())->method('getProductCollection') - ->will($this->returnValue($collection)); - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $context = $objectManager->getObject( \Magento\Framework\App\Action\Context::class, diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php index b941008bae0ae..683070c286239 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php @@ -10,8 +10,10 @@ use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface; use Magento\CatalogSearch\Test\Unit\Model\ResourceModel\BaseCollection; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory; /** * Tests Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection @@ -107,6 +109,17 @@ protected function setUp() ->method('create') ->willReturn($searchResultApplier); + $totalRecordsResolver = $this->getMockBuilder(TotalRecordsResolverInterface::class) + ->disableOriginalConstructor() + ->setMethods(['resolve']) + ->getMockForAbstractClass(); + $totalRecordsResolverFactory = $this->getMockBuilder(TotalRecordsResolverFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $totalRecordsResolverFactory->expects($this->any()) + ->method('create') + ->willReturn($totalRecordsResolver); $this->advancedCollection = $this->objectManager->getObject( \Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection::class, @@ -121,7 +134,8 @@ protected function setUp() 'productLimitationFactory' => $productLimitationFactoryMock, 'collectionProvider' => null, 'searchCriteriaResolverFactory' => $searchCriteriaResolverFactory, - 'searchResultApplierFactory' => $searchResultApplierFactory + 'searchResultApplierFactory' => $searchResultApplierFactory, + 'totalRecordsResolverFactory' => $totalRecordsResolverFactory ] ); } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php index 93ef42c8d5798..9170b81dc3182 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php @@ -8,7 +8,9 @@ use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface; use Magento\CatalogSearch\Test\Unit\Model\ResourceModel\BaseCollection; use Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory; use PHPUnit_Framework_MockObject_MockObject as MockObject; @@ -125,6 +127,18 @@ protected function setUp() ->method('create') ->willReturn($searchResultApplier); + $totalRecordsResolver = $this->getMockBuilder(TotalRecordsResolverInterface::class) + ->disableOriginalConstructor() + ->setMethods(['resolve']) + ->getMockForAbstractClass(); + $totalRecordsResolverFactory = $this->getMockBuilder(TotalRecordsResolverFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $totalRecordsResolverFactory->expects($this->any()) + ->method('create') + ->willReturn($totalRecordsResolver); + $this->model = $this->objectManager->getObject( \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection::class, [ @@ -134,7 +148,8 @@ protected function setUp() 'temporaryStorageFactory' => $temporaryStorageFactory, 'productLimitationFactory' => $productLimitationFactoryMock, 'searchCriteriaResolverFactory' => $searchCriteriaResolverFactory, - 'searchResultApplierFactory' => $searchResultApplierFactory + 'searchResultApplierFactory' => $searchResultApplierFactory, + 'totalRecordsResolverFactory' => $totalRecordsResolverFactory, ] ); diff --git a/app/code/Magento/CatalogSearch/etc/di.xml b/app/code/Magento/CatalogSearch/etc/di.xml index 26eede113be7c..5c80c92141727 100644 --- a/app/code/Magento/CatalogSearch/etc/di.xml +++ b/app/code/Magento/CatalogSearch/etc/di.xml @@ -16,6 +16,7 @@ + diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml index 9f61136bb1893..c7c9fb9cdfbe9 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml +++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml @@ -50,6 +50,6 @@ $productList = $block->getProductListHtml(); getResultCount()): ?> - + getSearchCriterias(); ?> diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php index be0d7d106a5dc..09968db00aa25 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php @@ -15,6 +15,8 @@ use Magento\Framework\App\ScopeResolverInterface; /** + * Query builder for search adapter. + * * @api * @since 100.1.0 */ diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php index f5847d8643d0a..6876b23bbb156 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php @@ -7,8 +7,6 @@ namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface - as FieldNameResolver; use Magento\Framework\App\ObjectManager; use Magento\Eav\Model\Config; use Magento\Catalog\Api\Data\ProductAttributeInterface; @@ -60,7 +58,7 @@ class StaticField implements FieldProviderInterface private $fieldIndexResolver; /** - * @var FieldNameResolver + * @var FieldName\ResolverInterface */ private $fieldNameResolver; @@ -71,7 +69,7 @@ class StaticField implements FieldProviderInterface * @param FieldTypeResolver $fieldTypeResolver * @param FieldIndexResolver $fieldIndexResolver * @param AttributeProvider $attributeAdapterProvider - * @param FieldNameResolver|null $fieldNameResolver + * @param FieldName\ResolverInterface|null $fieldNameResolver */ public function __construct( Config $eavConfig, @@ -80,7 +78,7 @@ public function __construct( FieldTypeResolver $fieldTypeResolver, FieldIndexResolver $fieldIndexResolver, AttributeProvider $attributeAdapterProvider, - FieldNameResolver $fieldNameResolver = null + FieldName\ResolverInterface $fieldNameResolver = null ) { $this->eavConfig = $eavConfig; $this->fieldTypeConverter = $fieldTypeConverter; @@ -89,7 +87,7 @@ public function __construct( $this->fieldIndexResolver = $fieldIndexResolver; $this->attributeAdapterProvider = $attributeAdapterProvider; $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(FieldNameResolver::class); + ->get(FieldName\ResolverInterface::class); } /** @@ -97,6 +95,7 @@ public function __construct( * * @param array $context * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getFields(array $context = []): array { diff --git a/app/code/Magento/Elasticsearch/Model/Layer/Category/ItemCollectionProvider.php b/app/code/Magento/Elasticsearch/Model/Layer/Category/ItemCollectionProvider.php new file mode 100644 index 0000000000000..ef2992e1fff9f --- /dev/null +++ b/app/code/Magento/Elasticsearch/Model/Layer/Category/ItemCollectionProvider.php @@ -0,0 +1,53 @@ +engineResolver = $engineResolver; + $this->factories = $factories; + } + + /** + * @inheritdoc + */ + public function getCollection(\Magento\Catalog\Model\Category $category) + { + if (!isset($this->factories[$this->engineResolver->getCurrentSearchEngine()])) { + throw new \DomainException('Undefined factory ' . $this->engineResolver->getCurrentSearchEngine()); + } + $collection = $this->factories[$this->engineResolver->getCurrentSearchEngine()]->create(); + $collection->addCategoryFilter($category); + + return $collection; + } +} diff --git a/app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php b/app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php index 16f0da24e3931..7d2a30b493d30 100644 --- a/app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php +++ b/app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php @@ -38,14 +38,15 @@ public function __construct( } /** - * @param \Magento\Catalog\Model\Category $category - * @return \Magento\Catalog\Model\ResourceModel\Product\Collection + * @inheritdoc */ public function getCollection(\Magento\Catalog\Model\Category $category) { if (!isset($this->factories[$this->engineResolver->getCurrentSearchEngine()])) { throw new \DomainException('Undefined factory ' . $this->engineResolver->getCurrentSearchEngine()); } - return $this->factories[$this->engineResolver->getCurrentSearchEngine()]->create(); + $collection = $this->factories[$this->engineResolver->getCurrentSearchEngine()]->create(); + + return $collection; } } diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Advanced/Collection.php deleted file mode 100644 index 15b1aab9edae7..0000000000000 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Advanced/Collection.php +++ /dev/null @@ -1,51 +0,0 @@ -addFieldToFilter('category_ids', $category->getId()); - $this->_productLimitationPrice(); - - return $this; - } - - /** - * @inheritdoc - */ - public function setVisibility($visibility) - { - $this->addFieldToFilter('visibility', $visibility); - return $this; - } -} diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection.php deleted file mode 100644 index 767f5d538a08d..0000000000000 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection.php +++ /dev/null @@ -1,54 +0,0 @@ -addFieldToFilter('category_ids', $category->getId()); - $this->_productLimitationPrice(); - - return $this; - } - - /** - * @inheritdoc - */ - public function setVisibility($visibility) - { - $this->addFieldToFilter('visibility', $visibility); - return $this; - } - - /** - * Set Order field - * - * @param string $attribute - * @param string $dir - * @return $this - */ - public function setOrder($attribute, $dir = Select::SQL_DESC) - { - if (is_array($attribute)) { - parent::setOrder($attribute, $dir); - } - parent::addOrder($attribute, $dir); - - return $this; - } -} diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php index 7115252c43bdc..6ffdf07e7be82 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php @@ -72,7 +72,7 @@ public function __construct( } /** - * @return SearchCriteria + * @inheritdoc */ public function resolve(): SearchCriteria { diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php index 4b76e56bb0526..3ae2d384782c3 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php @@ -38,7 +38,7 @@ public function __construct( } /** - * @return void + * @inheritdoc */ public function apply() { @@ -51,10 +51,9 @@ public function apply() $ids[] = (int)$item->getId(); } $this->collection->setPageSize(null); - $this->collection->cleanOrder(); $this->collection->getSelect()->where('e.entity_id IN (?)', $ids); $orderList = join(',', $ids); $this->collection->getSelect()->reset(\Magento\Framework\DB\Select::ORDER); - $this->collection->getSelect()->order("FIELD(e.entity_id,${orderList})"); + $this->collection->getSelect()->order("FIELD(e.entity_id,$orderList)"); } } diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php new file mode 100644 index 0000000000000..109721fcc71a9 --- /dev/null +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php @@ -0,0 +1,38 @@ +searchResult = $searchResult; + } + + /** + * @inheritdoc + */ + public function resolve(): ?int + { + return $this->searchResult->getTotalCount(); + } +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php b/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php index bf5f00781dae7..6f9ef552351fd 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php @@ -68,8 +68,7 @@ public function __construct( } /** - * @param RequestInterface $request - * @return QueryResponse + * @inheritdoc */ public function query(RequestInterface $request) { diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php index 5108f03026d19..d0aaa4b3dd572 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php @@ -10,6 +10,8 @@ use Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Query\Builder as Elasticsearch5Builder; /** + * Query builder for search adapter. + * * @api * @since 100.1.0 */ diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php index 3b12a02190657..5ccf202e3812b 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Elasticsearch\SearchAdapter\Query\Builder; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; @@ -87,10 +88,11 @@ public function getSort(RequestInterface $request) $fieldName = $this->map[$fieldName]; } if ($attribute->isSortable() && !($attribute->isFloatType() || $attribute->isIntegerType())) { - $fieldName .= '.' . $this->fieldNameResolver->getFieldName( - $attribute, - ['type' => FieldMapperInterface::TYPE_SORT] - ); + $suffix = $this->fieldNameResolver->getFieldName( + $attribute, + ['type' => FieldMapperInterface::TYPE_SORT] + ); + $fieldName .= '.' . $suffix; } $sorts[] = [ $fieldName => [ diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php index b1305e8ee601f..61abd905c44aa 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php @@ -59,6 +59,7 @@ protected function setUp() } /** + * @SuppressWarnings(PHPMD.UnusedLocalVariable) * @dataProvider getSortProvider * @param array $sortItems * @param $isSortable diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index f3cd640b20f0a..4e36c5e21c8c4 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -16,9 +16,12 @@ - + quick_search_container + elasticsearchSearchCriteriaResolverFactory + elasticsearchSearchResultApplier\Factory + elasticsearchTotalRecordsResolver\Factory @@ -29,15 +32,18 @@ - Magento\CatalogSearch\Model\ResourceModel\Fulltext\CollectionFactory + Magento\CatalogSearch\Model\ResourceModel\Fulltext\SearchCollectionFactory elasticsearchFulltextSearchCollectionFactory elasticsearchFulltextSearchCollectionFactory - + catalog_view_container + elasticsearchSearchCriteriaResolverFactory + elasticsearchSearchResultApplier\Factory + elasticsearchTotalRecordsResolver\Factory @@ -45,7 +51,7 @@ elasticsearchCategoryCollection - + Magento\CatalogSearch\Model\ResourceModel\Fulltext\CollectionFactory @@ -54,9 +60,12 @@ - + advanced_search_container + elasticsearchSearchCriteriaResolverFactory + elasticsearchSearchResultApplier\Factory + elasticsearchTotalRecordsResolver\Factory @@ -80,12 +89,6 @@ - - - - - - Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolver @@ -96,18 +99,11 @@ Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplier - + - elasticsearchSearchCriteriaResolverFactory - elasticsearchSearchResultApplier\Factory + Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolver - - - - elasticsearchSearchCriteriaResolverFactory - elasticsearchSearchResultApplier\Factory - - + @@ -455,6 +451,7 @@ Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\Converter Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\IndexResolver \Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\CompositeResolver + \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface diff --git a/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php b/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php index ab54699c03730..f35d6eac27ea8 100644 --- a/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php +++ b/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php @@ -7,6 +7,9 @@ use Magento\Framework\Event\ObserverInterface; +/** + * Review block observer. + */ class CatalogBlockProductCollectionBeforeToHtmlObserver implements ObserverInterface { /** diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php index 4c0fed148aea8..f0177c449a3f9 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php @@ -102,8 +102,7 @@ public function testFileSchemaUsingInvalidXml($expectedErrors = null) Element 'metric', attribute 'type': [facet 'enumeration'] " . "The value 'sumasdasd' is not an element of the set {'sum', 'count', 'min', 'max', 'avg'}. Element 'metric', attribute 'type': 'sumasdasd' is not a valid value of the local atomic type. -Element 'bucket': Missing child element(s). Expected is one of ( metrics, ranges ). -Element 'request': Missing child element(s). Expected is ( from )." +Element 'bucket': Missing child element(s). Expected is one of ( metrics, ranges )." ) ); parent::testFileSchemaUsingInvalidXml($expectedErrors); diff --git a/lib/internal/Magento/Framework/Search/Request.php b/lib/internal/Magento/Framework/Search/Request.php index 7ea3a271b4d84..60f3338046613 100644 --- a/lib/internal/Magento/Framework/Search/Request.php +++ b/lib/internal/Magento/Framework/Search/Request.php @@ -90,7 +90,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getName() { @@ -98,7 +98,7 @@ public function getName() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIndex() { @@ -106,7 +106,7 @@ public function getIndex() } /** - * {@inheritdoc} + * @inheritdoc */ public function getDimensions() { @@ -114,7 +114,7 @@ public function getDimensions() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAggregation() { @@ -122,7 +122,7 @@ public function getAggregation() } /** - * {@inheritdoc} + * @inheritdoc */ public function getQuery() { @@ -130,7 +130,7 @@ public function getQuery() } /** - * {@inheritdoc} + * @inheritdoc */ public function getFrom() { @@ -138,7 +138,7 @@ public function getFrom() } /** - * {@inheritdoc} + * @inheritdoc */ public function getSize() { @@ -146,7 +146,7 @@ public function getSize() } /** - * {@inheritdoc} + * @inheritdoc */ public function getSort() { diff --git a/lib/internal/Magento/Framework/Search/Request/Binder.php b/lib/internal/Magento/Framework/Search/Request/Binder.php index ef701c9cfc941..9df1ee87eb869 100644 --- a/lib/internal/Magento/Framework/Search/Request/Binder.php +++ b/lib/internal/Magento/Framework/Search/Request/Binder.php @@ -6,6 +6,8 @@ namespace Magento\Framework\Search\Request; /** + * Data binder for search request. + * * @api */ class Binder @@ -51,6 +53,8 @@ private function processLimits($data, $bindData) } /** + * Dimensions process. + * * @param array $data * @param array $bindData * @return array diff --git a/lib/internal/Magento/Framework/Search/Request/Builder.php b/lib/internal/Magento/Framework/Search/Request/Builder.php index d89abcf718b6f..74bc65010a934 100644 --- a/lib/internal/Magento/Framework/Search/Request/Builder.php +++ b/lib/internal/Magento/Framework/Search/Request/Builder.php @@ -11,7 +11,10 @@ use Magento\Framework\Search\RequestInterface; /** + * Search request builder. + * * @api + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Builder { @@ -96,7 +99,9 @@ public function setFrom($from) } /** - * @param $sort + * Set sort. + * + * @param \Magento\Framework\Api\SortOrder[] $sort * @return $this */ public function setSort($sort) @@ -229,6 +234,8 @@ private function convert($data) } /** + * Build dimensions. + * * @param array $dimensionsData * @return array */ diff --git a/lib/internal/Magento/Framework/Search/Response/QueryResponse.php b/lib/internal/Magento/Framework/Search/Response/QueryResponse.php index 64ad5fd827ec1..90c7056ea2549 100644 --- a/lib/internal/Magento/Framework/Search/Response/QueryResponse.php +++ b/lib/internal/Magento/Framework/Search/Response/QueryResponse.php @@ -47,7 +47,8 @@ public function __construct(array $documents, AggregationInterface $aggregations } /** - * Countable: return count of fields in document + * Countable: return count of fields in document. + * * @return int */ public function count() @@ -66,7 +67,7 @@ public function getIterator() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAggregations() { @@ -74,7 +75,7 @@ public function getAggregations() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTotal(): int { diff --git a/lib/internal/Magento/Framework/Search/Search.php b/lib/internal/Magento/Framework/Search/Search.php index cae40e1a6afba..fe228546b55fb 100644 --- a/lib/internal/Magento/Framework/Search/Search.php +++ b/lib/internal/Magento/Framework/Search/Search.php @@ -10,6 +10,9 @@ use Magento\Framework\App\ScopeResolverInterface; use Magento\Framework\Search\Request\Builder; +/** + * Search API for all requests. + */ class Search implements SearchInterface { /** @@ -51,7 +54,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function search(SearchCriteriaInterface $searchCriteria) { diff --git a/lib/internal/Magento/Framework/Search/SearchResponseBuilder.php b/lib/internal/Magento/Framework/Search/SearchResponseBuilder.php index 3d9f7e4424935..2314252f4609c 100644 --- a/lib/internal/Magento/Framework/Search/SearchResponseBuilder.php +++ b/lib/internal/Magento/Framework/Search/SearchResponseBuilder.php @@ -9,6 +9,9 @@ use Magento\Framework\Api\Search\DocumentFactory; use Magento\Framework\Api\Search\SearchResultFactory; +/** + * Builder for search response. + */ class SearchResponseBuilder { /** @@ -35,6 +38,8 @@ public function __construct( } /** + * Build search result by search response. + * * @param ResponseInterface $response * @return SearchResultInterface */ diff --git a/lib/internal/Magento/Framework/Search/etc/requests.xsd b/lib/internal/Magento/Framework/Search/etc/requests.xsd index a49124748b295..7d277382b698f 100644 --- a/lib/internal/Magento/Framework/Search/etc/requests.xsd +++ b/lib/internal/Magento/Framework/Search/etc/requests.xsd @@ -28,8 +28,8 @@ - - + + From c3025f97669bc3cb46742df67598a19bcf9d173c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20F=C3=BChr?= Date: Wed, 30 Jan 2019 16:03:13 +0100 Subject: [PATCH 008/108] 14857: prevent cache drop for frontend caches on sitemap generation https://github.com/magento/magento2/issues/14857 Introduces a (dummy) cache tag for frontend in sitemap and robots generation. This prevents the default and page_cache to be dropped completely. The cache tag for full_page cache is not modified. --- app/code/Magento/Robots/Model/Config/Value.php | 2 +- app/code/Magento/Sitemap/Model/Sitemap.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Robots/Model/Config/Value.php b/app/code/Magento/Robots/Model/Config/Value.php index c4e17e55f1262..5384fbb3bcd93 100644 --- a/app/code/Magento/Robots/Model/Config/Value.php +++ b/app/code/Magento/Robots/Model/Config/Value.php @@ -37,7 +37,7 @@ class Value extends ConfigValue implements IdentityInterface * @var string * @since 100.2.0 */ - protected $_cacheTag = true; + protected $_cacheTag = [self::CACHE_TAG]; /** * @var StoreManagerInterface diff --git a/app/code/Magento/Sitemap/Model/Sitemap.php b/app/code/Magento/Sitemap/Model/Sitemap.php index d58ff732c81d7..1dd95f5924ed5 100644 --- a/app/code/Magento/Sitemap/Model/Sitemap.php +++ b/app/code/Magento/Sitemap/Model/Sitemap.php @@ -159,7 +159,7 @@ class Sitemap extends \Magento\Framework\Model\AbstractModel implements \Magento * @var string * @since 100.2.0 */ - protected $_cacheTag = true; + protected $_cacheTag = [Value::CACHE_TAG]; /** * Item resolver From b9190b3e0dc8118043d10d3c652243fc439ac20c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20F=C3=BChr?= Date: Wed, 30 Jan 2019 16:03:13 +0100 Subject: [PATCH 009/108] 14857: prevent cache drop for frontend caches on sitemap generation https://github.com/magento/magento2/issues/14857 Introduces a (dummy) cache tag for frontend in sitemap and robots generation. This prevents the default and page_cache to be dropped completely. The cache tag for full_page cache is not modified. --- app/code/Magento/Robots/Model/Config/Value.php | 2 +- app/code/Magento/Sitemap/Model/Sitemap.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Robots/Model/Config/Value.php b/app/code/Magento/Robots/Model/Config/Value.php index c4e17e55f1262..5384fbb3bcd93 100644 --- a/app/code/Magento/Robots/Model/Config/Value.php +++ b/app/code/Magento/Robots/Model/Config/Value.php @@ -37,7 +37,7 @@ class Value extends ConfigValue implements IdentityInterface * @var string * @since 100.2.0 */ - protected $_cacheTag = true; + protected $_cacheTag = [self::CACHE_TAG]; /** * @var StoreManagerInterface diff --git a/app/code/Magento/Sitemap/Model/Sitemap.php b/app/code/Magento/Sitemap/Model/Sitemap.php index d58ff732c81d7..1dd95f5924ed5 100644 --- a/app/code/Magento/Sitemap/Model/Sitemap.php +++ b/app/code/Magento/Sitemap/Model/Sitemap.php @@ -159,7 +159,7 @@ class Sitemap extends \Magento\Framework\Model\AbstractModel implements \Magento * @var string * @since 100.2.0 */ - protected $_cacheTag = true; + protected $_cacheTag = [Value::CACHE_TAG]; /** * Item resolver From ebc266b5e0f229c644a4329f7463696020b6a681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20F=C3=BChr?= Date: Thu, 31 Jan 2019 13:58:32 +0100 Subject: [PATCH 010/108] amend touched DocBlocks --- app/code/Magento/Robots/Model/Config/Value.php | 3 +-- app/code/Magento/Sitemap/Model/Sitemap.php | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Robots/Model/Config/Value.php b/app/code/Magento/Robots/Model/Config/Value.php index 5384fbb3bcd93..5ccfa12334607 100644 --- a/app/code/Magento/Robots/Model/Config/Value.php +++ b/app/code/Magento/Robots/Model/Config/Value.php @@ -32,9 +32,8 @@ class Value extends ConfigValue implements IdentityInterface const CACHE_TAG = 'robots'; /** - * Model cache tag for clear cache in after save and after delete + * @inheritdoc * - * @var string * @since 100.2.0 */ protected $_cacheTag = [self::CACHE_TAG]; diff --git a/app/code/Magento/Sitemap/Model/Sitemap.php b/app/code/Magento/Sitemap/Model/Sitemap.php index 1dd95f5924ed5..01c4f4186cf48 100644 --- a/app/code/Magento/Sitemap/Model/Sitemap.php +++ b/app/code/Magento/Sitemap/Model/Sitemap.php @@ -154,9 +154,8 @@ class Sitemap extends \Magento\Framework\Model\AbstractModel implements \Magento protected $dateTime; /** - * Model cache tag for clear cache in after save and after delete + * @inheritdoc * - * @var string * @since 100.2.0 */ protected $_cacheTag = [Value::CACHE_TAG]; From 2cfeec7fa232cc93af40de2338874afd977e7a41 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza Date: Sat, 2 Feb 2019 17:03:29 +0100 Subject: [PATCH 011/108] Cart address id added to the address resolver --- .../QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php | 1 + app/code/Magento/QuoteGraphQl/etc/schema.graphqls | 1 + 2 files changed, 2 insertions(+) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php index fb742477ec99b..a79d61ce86f67 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php @@ -71,6 +71,7 @@ public function getCartAddresses(CartInterface $cart): array private function extractAddressData(QuoteAddress $address): array { $addressData = [ + 'cart_address_id' => $address->getId(), 'country' => [ 'code' => $address->getCountryId(), 'label' => $address->getCountry() diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 4c1101a5f90a8..12ef185c113ae 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -101,6 +101,7 @@ type Cart { } type CartAddress { + cart_address_id: String firstname: String lastname: String company: String From 15c93775358004348f0225c7cfb445f2832374e5 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza Date: Sat, 2 Feb 2019 17:29:14 +0100 Subject: [PATCH 012/108] Modified input schema and resolver --- .../Resolver/SetShippingMethodsOnCart.php | 25 +++++++++++-------- .../Magento/QuoteGraphQl/etc/schema.graphqls | 10 +++++--- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php index 920829f5d67b1..2e38bde2c9618 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php @@ -57,26 +57,29 @@ public function __construct( */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { - $shippingMethods = $this->arrayManager->get('input/shipping_methods', $args); + $shippingAddresses = $this->arrayManager->get('input/shipping_addresses', $args); $maskedCartId = $this->arrayManager->get('input/cart_id', $args); if (!$maskedCartId) { throw new GraphQlInputException(__('Required parameter "cart_id" is missing')); } - if (!$shippingMethods) { + if (!$shippingAddresses) { throw new GraphQlInputException(__('Required parameter "shipping_methods" is missing')); } - $shippingMethod = reset($shippingMethods); // This point can be extended for multishipping + $shippingAddress = reset($shippingAddresses); // This point can be extended for multishipping - if (!$shippingMethod['cart_address_id']) { + if (!$shippingAddress['cart_address_id']) { throw new GraphQlInputException(__('Required parameter "cart_address_id" is missing')); } - if (!$shippingMethod['shipping_carrier_code']) { - throw new GraphQlInputException(__('Required parameter "shipping_carrier_code" is missing')); + if (!isset($shippingAddress['shipping_method'])) { + throw new GraphQlInputException(__('Required parameter "shipping_method" is missing')); } - if (!$shippingMethod['shipping_method_code']) { - throw new GraphQlInputException(__('Required parameter "shipping_method_code" is missing')); + if (!$shippingAddress['shipping_method']['carrier_code']) { + throw new GraphQlInputException(__('Required parameter "carrier_code" is missing')); + } + if (!$shippingAddress['shipping_method']['method_code']) { + throw new GraphQlInputException(__('Required parameter "method_code" is missing')); } $userId = $context->getUserId(); @@ -84,9 +87,9 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $this->setShippingMethodOnCart->execute( $cart, - $shippingMethod['cart_address_id'], - $shippingMethod['shipping_carrier_code'], - $shippingMethod['shipping_method_code'] + $shippingAddress['cart_address_id'], + $shippingAddress['shipping_method']['carrier_code'], + $shippingAddress['shipping_method']['method_code'] ); return [ diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 12ef185c113ae..a507a4a68b012 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -55,13 +55,17 @@ input CartAddressInput { input SetShippingMethodsOnCartInput { cart_id: String! - shipping_methods: [ShippingMethodForAddressInput!]! + shipping_addresses: [ShippingMethodForAddressInput!]! } input ShippingMethodForAddressInput { cart_address_id: Int! - shipping_carrier_code: String! - shipping_method_code: String! + shipping_method: ShippingMethodInput! +} + +input ShippingMethodInput { + carrier_code: String! + method_code: String! } type SetBillingAddressOnCartOutput { From c88e97523b5362935f9fefc3051983ec64e24f1c Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza Date: Tue, 5 Feb 2019 16:54:28 +0100 Subject: [PATCH 013/108] Updated logic to match a new schema --- .../Cart/Address/AddressDataProvider.php | 37 +++++++++----- .../{CartAddresses.php => BillingAddress.php} | 4 +- .../Model/Resolver/ShippingAddresses.php | 48 +++++++++++++++++++ .../Magento/QuoteGraphQl/etc/schema.graphqls | 8 ++-- .../Quote/SetShippingMethodOnCartTest.php | 27 ++++++----- 5 files changed, 93 insertions(+), 31 deletions(-) rename app/code/Magento/QuoteGraphQl/Model/Resolver/{CartAddresses.php => BillingAddress.php} (90%) create mode 100644 app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php index a79d61ce86f67..c4c19ac63d664 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php @@ -36,27 +36,45 @@ public function __construct( } /** - * Collect and return information about shipping and billing addresses + * Collect and return information about shipping addresses * * @param CartInterface $cart * @return array */ - public function getCartAddresses(CartInterface $cart): array + public function getShippingAddresses(CartInterface $cart): array { $addressData = []; $shippingAddress = $cart->getShippingAddress(); - $billingAddress = $cart->getBillingAddress(); if ($shippingAddress) { $shippingData = $this->dataObjectConverter->toFlatArray($shippingAddress, [], AddressInterface::class); - $shippingData['address_type'] = 'SHIPPING'; - $addressData[] = array_merge($shippingData, $this->extractAddressData($shippingAddress)); + $shippingMethodData = explode('_', $shippingAddress->getShippingMethod()); + $shippingData['selected_shipping_method'] = [ + 'carrier_code' => $shippingMethodData[0], + 'method_code' => $shippingMethodData[1], + 'label' => $shippingAddress->getShippingDescription(), + 'free_shipping' => $shippingAddress->getFreeShipping() + ]; + $addressData['shipping_addresses'] = array_merge($shippingData, $this->extractAddressData($shippingAddress)); } + return $addressData; + } + + /** + * Collect and return information about billing address + * + * @param CartInterface $cart + * @return array + */ + public function getBillingAddress(CartInterface $cart): array + { + $addressData = []; + $billingAddress = $cart->getBillingAddress(); + if ($billingAddress) { $billingData = $this->dataObjectConverter->toFlatArray($billingAddress, [], AddressInterface::class); - $billingData['address_type'] = 'BILLING'; - $addressData[] = array_merge($billingData, $this->extractAddressData($billingAddress)); + $addressData['billing_address'] = array_merge($billingData, $this->extractAddressData($billingAddress)); } return $addressData; @@ -81,11 +99,6 @@ private function extractAddressData(QuoteAddress $address): array 'label' => $address->getRegion() ], 'street' => $address->getStreet(), - 'selected_shipping_method' => [ - 'code' => $address->getShippingMethod(), - 'label' => $address->getShippingDescription(), - 'free_shipping' => $address->getFreeShipping(), - ], 'items_weight' => $address->getWeight(), 'customer_notes' => $address->getCustomerNotes() ]; diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php similarity index 90% rename from app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddresses.php rename to app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php index 69544672bf12e..feb3265e20c51 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddresses.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php @@ -16,7 +16,7 @@ /** * @inheritdoc */ -class CartAddresses implements ResolverInterface +class BillingAddress implements ResolverInterface { /** * @var AddressDataProvider @@ -43,6 +43,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $cart = $value['model']; - return $this->addressDataProvider->getCartAddresses($cart); + return $this->addressDataProvider->getBillingAddress($cart); } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php new file mode 100644 index 0000000000000..b725dd1e9f1ca --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php @@ -0,0 +1,48 @@ +addressDataProvider = $addressDataProvider; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" value should be specified')); + } + + $cart = $value['model']; + + return $this->addressDataProvider->getShippingAddresses($cart); + } +} diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index a507a4a68b012..ad16bbde4d429 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -101,7 +101,8 @@ type Cart { cart_id: String items: [CartItemInterface] applied_coupon: AppliedCoupon - addresses: [CartAddress]! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartAddresses") + shipping_addresses: [CartAddress] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddresses") + billing_address: CartAddress @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\BillingAddress") } type CartAddress { @@ -115,7 +116,6 @@ type CartAddress { postcode: String country: CartAddressCountry telephone: String - address_type: AdressTypeEnum selected_shipping_method: CheckoutShippingMethod available_shipping_methods: [CheckoutShippingMethod] items_weight: Float @@ -139,10 +139,10 @@ type CartAddressCountry { } type CheckoutShippingMethod { - code: String + carrier_code: String + method_code: String label: String free_shipping: Boolean! - error_message: String # TODO: Add more complex structure for shipping rates } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodOnCartTest.php index 7e77284c6b220..83d45152e0ec4 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodOnCartTest.php @@ -79,12 +79,11 @@ public function testSetShippingMethodOnCart() self::assertArrayHasKey('setShippingMethodsOnCart', $response); self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); self::assertEquals($maskedQuoteId, $response['setShippingMethodsOnCart']['cart']['cart_id']); - $addressesInformation = $response['setShippingMethodsOnCart']['cart']['addresses']; - self::assertCount(2, $addressesInformation); + $addressesInformation = $response['setShippingMethodsOnCart']['cart']['shipping_addresses']; + self::assertCount(1, $addressesInformation); + self::assertEquals($addressesInformation[0]['selected_shipping_method']['carrier_code'], $shippingCarrierCode); self::assertEquals( - $addressesInformation[0]['selected_shipping_method']['code'], - $shippingCarrierCode . '_' . $shippingMethodCode - ); + $addressesInformation[0]['selected_shipping_method']['method_code'], $shippingMethodCode); } /** @@ -211,19 +210,21 @@ private function prepareMutationQuery( setShippingMethodsOnCart(input: { cart_id: "$maskedQuoteId", - shipping_methods: [ - { - shipping_method_code: "$shippingMethodCode" - shipping_carrier_code: "$shippingCarrierCode" - cart_address_id: $shippingAddressId + shipping_addresses: [{ + cart_address_id: $shippingAddressId + shipping_method: { + method_code: "$shippingMethodCode" + carrier_code: "$shippingCarrierCode" } - ]}) { + }] + }) { cart { cart_id, - addresses { + shipping_addresses { selected_shipping_method { - code + carrier_code + method_code label } } From 7fc0ff766a4ca185366a8d5c52c1197a3b067131 Mon Sep 17 00:00:00 2001 From: Prakash Date: Fri, 8 Feb 2019 19:34:23 +0530 Subject: [PATCH 014/108] Fixes for product tabbing issue --- lib/web/mage/tabs.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/web/mage/tabs.js b/lib/web/mage/tabs.js index b441477ab8d8a..e4d196fcbbca8 100644 --- a/lib/web/mage/tabs.js +++ b/lib/web/mage/tabs.js @@ -71,6 +71,9 @@ define([ anchorId = anchor.replace('#', ''); if (anchor && isValid) { + if(anchorId == 'review-form'){ + anchorId = anchorId.replace('-form', 's'); + } $.each(self.contents, function (i) { if ($(this).attr('id') === anchorId) { self.collapsibles.not(self.collapsibles.eq(i)).collapsible('forceDeactivate'); From 91ff1c5d21281dc015942f92766e09cd012dcbbb Mon Sep 17 00:00:00 2001 From: Pieter Hoste Date: Sat, 9 Feb 2019 10:29:04 +0100 Subject: [PATCH 015/108] Also populate the storesCache when importing product only on storeview(s) level, previously the storesCache was only populated for products imported on global level. This causes problems with the correct url rewrites to be generated. --- .../CatalogUrlRewrite/Observer/AfterImportDataObserver.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php index 9aaa384776855..7b60c85049767 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php @@ -263,6 +263,7 @@ protected function _populateForUrlGeneration($rowData) if ($this->isGlobalScope($product->getStoreId())) { $this->populateGlobalProduct($product); } else { + $this->storesCache[$product->getStoreId()] = true; $this->addProductToImport($product, $product->getStoreId()); } return $this; From 53694fa9941f5111c6798c962ee7a5fb0b78ca20 Mon Sep 17 00:00:00 2001 From: Oshan Date: Mon, 11 Feb 2019 17:15:38 +0530 Subject: [PATCH 016/108] Remove unused use statement in Wishlist Allcart Controller --- app/code/Magento/Wishlist/Controller/Index/Allcart.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/Wishlist/Controller/Index/Allcart.php b/app/code/Magento/Wishlist/Controller/Index/Allcart.php index 8463a00c866c5..7035547ec5224 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Allcart.php +++ b/app/code/Magento/Wishlist/Controller/Index/Allcart.php @@ -6,7 +6,6 @@ namespace Magento\Wishlist\Controller\Index; use Magento\Framework\Data\Form\FormKey\Validator; -use Magento\Framework\App\Action; use Magento\Framework\App\Action\Context; use Magento\Wishlist\Controller\WishlistProviderInterface; use Magento\Wishlist\Model\ItemCarrier; From 9de6daf7883ce32929f70f02640bd759fc0892aa Mon Sep 17 00:00:00 2001 From: Dmytro Cheshun Date: Mon, 11 Feb 2019 21:38:20 +0200 Subject: [PATCH 017/108] Fix static test --- app/code/Magento/Wishlist/Controller/Index/Allcart.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Wishlist/Controller/Index/Allcart.php b/app/code/Magento/Wishlist/Controller/Index/Allcart.php index 7035547ec5224..08aaea5e4f425 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Allcart.php +++ b/app/code/Magento/Wishlist/Controller/Index/Allcart.php @@ -11,6 +11,9 @@ use Magento\Wishlist\Model\ItemCarrier; use Magento\Framework\Controller\ResultFactory; +/** + * Action Add All to Cart + */ class Allcart extends \Magento\Wishlist\Controller\AbstractIndex { /** From 57728f2a50c2ae7d788d37c35ff0fd0beb9dad2e Mon Sep 17 00:00:00 2001 From: Vitaliy Honcharenko Date: Mon, 11 Feb 2019 14:41:20 -0600 Subject: [PATCH 018/108] MAGETWO-95294: Mysql search slow on the catalog page - changes after CR --- .../ResourceModel/Fulltext/Collection.php | 2 -- .../Collection/TotalRecordsResolver.php | 2 ++ .../frontend/templates/advanced/result.phtml | 3 +-- .../AttributeAdapter/DummyAttribute.php | 20 ++++++++++++++++ .../SearchAdapter/Query/Builder/SortTest.php | 23 +++++++++++++++++++ app/code/Magento/Elasticsearch/etc/di.xml | 23 +++++++++++++++++-- 6 files changed, 67 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index 7a5cb1ebb3a0e..a978465682527 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -425,8 +425,6 @@ protected function _renderFiltersBefore() $this->getSearchResultApplier($this->searchResult)->apply(); parent::_renderFiltersBefore(); - - $this->_eventManager->dispatch('catalog_search_collection_render_filters_before', ['collection' => $this]); } /** diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php index 713035ff43db7..12b6f81313913 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php @@ -8,6 +8,8 @@ /** * Resolve total records count. + * + * For Mysql search engine we can't resolve total record count before full load of collection. */ class TotalRecordsResolver implements TotalRecordsResolverInterface { diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml index c7c9fb9cdfbe9..6bdca48f499c3 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml +++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml @@ -11,7 +11,6 @@ /** * @var $block \Magento\CatalogSearch\Block\Advanced\Result */ -$productList = $block->getProductListHtml(); ?> getResultCount()): ?> getResultCount()): ?> - + getSearchCriterias(); ?> diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter/DummyAttribute.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter/DummyAttribute.php index b8c0da53c53e0..19b9f85c44b03 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter/DummyAttribute.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter/DummyAttribute.php @@ -59,4 +59,24 @@ public function setCustomAttributes(array $attributes) { return $this; } + + /** + * Get property value that guarantee of using an attribute in sort purposes on the storefront. + * + * @return bool + */ + public function getUsedForSortBy() + { + return false; + } + + /** + * Dummy attribute doesn't have backend type. + * + * @return null + */ + public function getBackendType() + { + return null; + } } diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php index 61abd905c44aa..aaebd162590f9 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php @@ -207,6 +207,29 @@ public function getSortProvider() ] ] ] + ], + [ + [ + [ + 'field' => 'entity_id', + 'direction' => 'DESC' + ], + [ + 'field' => 'not_eav_attribute', + 'direction' => 'DESC' + ], + ], + false, + false, + false, + 'not_eav_attribute', + [ + [ + 'not_eav_attribute' => [ + 'order' => 'desc' + ] + ] + ] ] ]; } diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index d0718ec21eedd..aa3e9550f511d 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -13,9 +13,28 @@ - - + + + elasticsearchLayerSearchItemCollectionProvider + Magento\CatalogSearch\Model\Layer\Search\StateKey + + + + + Magento\Elasticsearch\Model\Layer\Search\Context + + + + + elasticsearchLayerCategoryItemCollectionProvider + + + + + Magento\Elasticsearch\Model\Layer\Category\Context + + quick_search_container From 01d7f5126d7ec13fd28e18c9bc4b65cc92f2faff Mon Sep 17 00:00:00 2001 From: Prakash Date: Wed, 13 Feb 2019 11:59:25 +0530 Subject: [PATCH 019/108] Fixes for tabbing issue on product --- lib/web/mage/tabs.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/web/mage/tabs.js b/lib/web/mage/tabs.js index e4d196fcbbca8..ee3119e90d3a8 100644 --- a/lib/web/mage/tabs.js +++ b/lib/web/mage/tabs.js @@ -70,12 +70,9 @@ define([ isValid = $.mage.isValidSelector(anchor), anchorId = anchor.replace('#', ''); - if (anchor && isValid) { - if(anchorId == 'review-form'){ - anchorId = anchorId.replace('-form', 's'); - } + if (anchor && isValid) { $.each(self.contents, function (i) { - if ($(this).attr('id') === anchorId) { + if ($(this).attr('id') === anchorId || $(this).find('#' + anchorId).length) { self.collapsibles.not(self.collapsibles.eq(i)).collapsible('forceDeactivate'); return false; From 8fafd29c40bff83a629970e79b052de29807a40a Mon Sep 17 00:00:00 2001 From: Ievgenii Gryshkun Date: Wed, 13 Feb 2019 14:42:45 +0200 Subject: [PATCH 020/108] Fix message when maxSaleQty is set and qty is more than maxSaleQty --- .../QuoteGraphQl/Model/Cart/AddProductsToCart.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php index 005cf3a10ca80..585323a01a91d 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php @@ -46,18 +46,17 @@ public function __construct( * @param array $cartItems * @throws GraphQlInputException * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException */ public function execute(Quote $cart, array $cartItems): void { foreach ($cartItems as $cartItemData) { - $this->addProductToCart->execute($cart, $cartItemData); - } - - if ($cart->getData('has_error')) { - throw new GraphQlInputException( - __('Shopping cart error: %message', ['message' => $this->getCartErrors($cart)]) - ); + try { + $this->addProductToCart->execute($cart, $cartItemData); + } catch (\Exception $error) { + throw new GraphQlInputException( + __('Shopping cart error: %message', ['message' => $error->getMessage()]) + ); + } } $this->cartRepository->save($cart); From 18663c069a01b6988a714643b4ef279c119456ca Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky Date: Wed, 13 Feb 2019 15:11:22 +0200 Subject: [PATCH 021/108] ENGCOM-4220: Static test fix. --- app/code/Magento/Wishlist/Controller/Index/Allcart.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Wishlist/Controller/Index/Allcart.php b/app/code/Magento/Wishlist/Controller/Index/Allcart.php index 08aaea5e4f425..958e0f49ca1b6 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Allcart.php +++ b/app/code/Magento/Wishlist/Controller/Index/Allcart.php @@ -5,6 +5,7 @@ */ namespace Magento\Wishlist\Controller\Index; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Framework\App\Action\Context; use Magento\Wishlist\Controller\WishlistProviderInterface; @@ -14,7 +15,7 @@ /** * Action Add All to Cart */ -class Allcart extends \Magento\Wishlist\Controller\AbstractIndex +class Allcart extends \Magento\Wishlist\Controller\AbstractIndex implements HttpPostActionInterface { /** * @var WishlistProviderInterface From c034338b65b835a875c7cfa6196e697f497fda4f Mon Sep 17 00:00:00 2001 From: Volodymyr Zaets Date: Wed, 13 Feb 2019 17:58:30 +0200 Subject: [PATCH 022/108] Remove spaces --- lib/web/mage/tabs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web/mage/tabs.js b/lib/web/mage/tabs.js index ee3119e90d3a8..65c452d33bf12 100644 --- a/lib/web/mage/tabs.js +++ b/lib/web/mage/tabs.js @@ -70,7 +70,7 @@ define([ isValid = $.mage.isValidSelector(anchor), anchorId = anchor.replace('#', ''); - if (anchor && isValid) { + if (anchor && isValid) { $.each(self.contents, function (i) { if ($(this).attr('id') === anchorId || $(this).find('#' + anchorId).length) { self.collapsibles.not(self.collapsibles.eq(i)).collapsible('forceDeactivate'); From 5c1b27da714ac785eab9b739f38dc60cf4b7e196 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky Date: Thu, 14 Feb 2019 16:52:25 +0200 Subject: [PATCH 023/108] ENGCOM-4220: Integration test fix. --- .../testsuite/Magento/Wishlist/Controller/IndexTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php index 92eae7a3fe3d7..940d05eb4d5d7 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php @@ -128,6 +128,7 @@ public function testAddActionProductNameXss() public function testAllcartAction() { $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class)->getFormKey(); + $this->getRequest()->setMethod('POST'); $this->getRequest()->setParam('form_key', $formKey); $this->dispatch('wishlist/index/allcart'); From 3d228f3cdc4be0ae233e0db57d4b1cbe1d840f6a Mon Sep 17 00:00:00 2001 From: Ievgenii Gryshkun Date: Fri, 15 Feb 2019 09:17:38 +0200 Subject: [PATCH 024/108] Fix message when maxSaleQty is set and qty is more than maxSaleQty --- .../Model/Cart/AddProductsToCart.php | 8 +-- .../Quote/AddSimpleProductToCartTest.php | 63 +++++++++++++++++-- .../Magento/Catalog/_files/products.php | 13 ++++ 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php index 585323a01a91d..6deb387476d5f 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php @@ -50,13 +50,7 @@ public function __construct( public function execute(Quote $cart, array $cartItems): void { foreach ($cartItems as $cartItemData) { - try { - $this->addProductToCart->execute($cart, $cartItemData); - } catch (\Exception $error) { - throw new GraphQlInputException( - __('Shopping cart error: %message', ['message' => $error->getMessage()]) - ); - } + $this->addProductToCart->execute($cart, $cartItemData); } $this->cartRepository->save($cart); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php index 4cbc614e1b8dc..28bf755911ce1 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php @@ -41,6 +41,23 @@ protected function setUp() $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); } + /** + * @magentoApiDataFixture Magento/Catalog/_files/products.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + */ + public function testAddSimpleProductsToCart() + { + $sku = 'simple'; + $qty = 2; + $maskedQuoteId = $this->getMaskedQuoteId(); + $query = $this->getQueryAddSimpleProduct($maskedQuoteId, $sku, $qty); + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']); + $cartQty = $response['addSimpleProductsToCart']['cart']['items'][0]['qty']; + + $this->assertEquals($qty, $cartQty); + } + /** * @magentoApiDataFixture Magento/Catalog/_files/products.php * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php @@ -52,14 +69,51 @@ public function testAddProductIfQuantityIsNotAvailable() $sku = 'simple'; $qty = 200; + $maskedQuoteId = $this->getMaskedQuoteId(); + $query = $this->getQueryAddSimpleProduct($maskedQuoteId, $sku, $qty); + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/products.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @expectedException \Exception + * @expectedExceptionMessage The most you may purchase is 10000. + */ + public function testAddMoreProductsThatAllowed() + { + $sku = 'simple-product-with-huge-amount'; + $qty = 20000; + $maskedQuoteId = $this->getMaskedQuoteId(); + $query = $this->getQueryAddSimpleProduct($maskedQuoteId, $sku, $qty); + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @return string + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function getMaskedQuoteId() + { $this->quoteResource->load( $this->quote, 'test_order_1', 'reserved_order_id' ); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + return $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + } - $query = <<graphQlQuery($query); } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products.php index 348701a99f287..407cc31e0c390 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products.php @@ -36,3 +36,16 @@ ->setStockData(['use_config_manage_stock' => 1, 'qty' => 24, 'is_in_stock' => 1]) ->setQty(24) ->save(); + +$productWithHugeQty = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Model\Product::class, ['data' => $product->getData()]); + +$productWithHugeQty->setUrlKey('simple-product-with-huge-amount') + ->setId(2) + ->setRowId(2) + ->setName('Simple Product With Huge Amount') + ->setSku('simple-product-with-huge-amount') + ->setCustomDesign('Magento/blank') + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50000, 'is_in_stock' => 1]) + ->setQty(50000) + ->save(); From 437d7f75ea5354de7d2c480e1cfc36ef4772a1a4 Mon Sep 17 00:00:00 2001 From: Evgeny Petrov Date: Mon, 18 Feb 2019 11:19:42 +0300 Subject: [PATCH 025/108] MAGETWO-62411: [Github] Cannot save product because of required attribute within Advanced pricing #7735 --- .../Product/Form/Modifier/AdvancedPricing.php | 3 +++ .../Ui/view/base/web/js/form/components/button.js | 3 ++- .../view/base/web/templates/form/element/button.html | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php index 2a4d2ff52d479..00132c6ad89e8 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php @@ -389,6 +389,9 @@ private function addAdvancedPriceLink() 'componentType' => Container::NAME, 'component' => 'Magento_Ui/js/form/components/button', 'template' => 'ui/form/components/button/container', + 'imports' => [ + 'childError' => $this->scopeName . '.advanced_pricing_modal.advanced-pricing:error', + ], 'actions' => [ [ 'targetName' => $this->scopeName . '.advanced_pricing_modal', diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/button.js b/app/code/Magento/Ui/view/base/web/js/form/components/button.js index df85af5824d92..bfc2eb2b8852b 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/button.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/button.js @@ -45,7 +45,8 @@ define([ .observe([ 'visible', 'disabled', - 'title' + 'title', + 'childError' ]); }, diff --git a/app/code/Magento/Ui/view/base/web/templates/form/element/button.html b/app/code/Magento/Ui/view/base/web/templates/form/element/button.html index a8b6c3a858956..766df35f5764b 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/element/button.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/element/button.html @@ -11,3 +11,15 @@ attr="'data-index': index"> + + + + + + + This element contains invalid data. Please resolve this before saving. + + + + From fbaac31183dd292fe09a48ebfdafc8992e0df23d Mon Sep 17 00:00:00 2001 From: Yuliya Labudova Date: Mon, 18 Feb 2019 12:34:54 +0300 Subject: [PATCH 026/108] MAGETWO-57337: Store View (language) switch leads to 404 - Redirect to home page if we haven't rewrite for target store (from old) --- .../Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php b/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php index 2ec573b6459da..e1bb094e7fc39 100644 --- a/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php +++ b/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php @@ -81,7 +81,12 @@ public function switch(StoreInterface $fromStore, StoreInterface $targetStore, s $existingRewrite = $this->urlFinder->findOneByData([ UrlRewrite::REQUEST_PATH => $urlPath ]); - if ($existingRewrite) { + $currentRewrite = $this->urlFinder->findOneByData([ + UrlRewrite::REQUEST_PATH => $urlPath, + UrlRewrite::STORE_ID => $targetStore->getId(), + ]); + + if ($existingRewrite && !$currentRewrite) { /** @var \Magento\Framework\App\Response\Http $response */ $targetUrl = $targetStore->getBaseUrl(); } From edb9c6ebbb92fd97d953a4160d6b48e6654af448 Mon Sep 17 00:00:00 2001 From: Nazarn96 Date: Mon, 18 Feb 2019 17:09:45 +0200 Subject: [PATCH 027/108] Fix-issue-21292 --- app/code/Magento/GoogleAnalytics/Block/Ga.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/GoogleAnalytics/Block/Ga.php b/app/code/Magento/GoogleAnalytics/Block/Ga.php index 7d065ea50b369..ac178d26cd490 100644 --- a/app/code/Magento/GoogleAnalytics/Block/Ga.php +++ b/app/code/Magento/GoogleAnalytics/Block/Ga.php @@ -205,7 +205,7 @@ public function getPageTrackingData($accountId) { return [ 'optPageUrl' => $this->getOptPageUrl(), - 'isAnonymizedIpActive' => $this->_googleAnalyticsData->isAnonymizedIpActive(), + 'isAnonymizedIpActive' => (int)$this->_googleAnalyticsData->isAnonymizedIpActive(), 'accountId' => $this->escapeHtmlAttr($accountId, false) ]; } From 3fbdc45b535aee0b08b770b564d03b8a70d920fb Mon Sep 17 00:00:00 2001 From: Ievgenii Gryshkun Date: Tue, 19 Feb 2019 09:23:29 +0200 Subject: [PATCH 028/108] Fix message when maxSaleQty is set and qty is more than maxSaleQty --- .../Quote/AddSimpleProductToCartTest.php | 28 +++++++++++++------ .../Magento/Catalog/_files/products.php | 13 --------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php index 28bf755911ce1..26c84fa0559e7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php @@ -12,6 +12,7 @@ use Magento\Quote\Model\Quote; use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\Config\Model\ResourceModel\Config; class AddSimpleProductToCartTest extends GraphQlAbstract { @@ -30,6 +31,11 @@ class AddSimpleProductToCartTest extends GraphQlAbstract */ private $quoteIdToMaskedId; + /** + * @var Config + */ + private $config; + /** * @inheritdoc */ @@ -39,6 +45,7 @@ protected function setUp() $this->quoteResource = $objectManager->get(QuoteResource::class); $this->quote = $objectManager->create(Quote::class); $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->config = $objectManager->get(Config::class); } /** @@ -60,15 +67,18 @@ public function testAddSimpleProductsToCart() /** * @magentoApiDataFixture Magento/Catalog/_files/products.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php * @expectedException \Exception - * @expectedExceptionMessage The requested qty is not available + * @expectedExceptionMessage The most you may purchase is 5. */ - public function testAddProductIfQuantityIsNotAvailable() + public function testAddMoreProductsThatAllowed() { $sku = 'simple'; - $qty = 200; + $qty = 7; + $maxQty = 5; + $this->config->saveConfig('cataloginventory/item_options/max_sale_qty', $maxQty, 'default', 0); $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getQueryAddSimpleProduct($maskedQuoteId, $sku, $qty); $this->graphQlQuery($query); @@ -78,23 +88,23 @@ public function testAddProductIfQuantityIsNotAvailable() * @magentoApiDataFixture Magento/Catalog/_files/products.php * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php * @expectedException \Exception - * @expectedExceptionMessage The most you may purchase is 10000. + * @expectedExceptionMessage The requested qty is not available */ - public function testAddMoreProductsThatAllowed() + public function testAddProductIfQuantityIsNotAvailable() { - $sku = 'simple-product-with-huge-amount'; - $qty = 20000; + $sku = 'simple'; + $qty = 200; + $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getQueryAddSimpleProduct($maskedQuoteId, $sku, $qty); $this->graphQlQuery($query); } /** - * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php * @return string * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function getMaskedQuoteId() + public function getMaskedQuoteId() : string { $this->quoteResource->load( $this->quote, diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products.php index 407cc31e0c390..348701a99f287 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products.php @@ -36,16 +36,3 @@ ->setStockData(['use_config_manage_stock' => 1, 'qty' => 24, 'is_in_stock' => 1]) ->setQty(24) ->save(); - -$productWithHugeQty = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Catalog\Model\Product::class, ['data' => $product->getData()]); - -$productWithHugeQty->setUrlKey('simple-product-with-huge-amount') - ->setId(2) - ->setRowId(2) - ->setName('Simple Product With Huge Amount') - ->setSku('simple-product-with-huge-amount') - ->setCustomDesign('Magento/blank') - ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50000, 'is_in_stock' => 1]) - ->setQty(50000) - ->save(); From e18e4ace01a29644f2d3e7dcf685e8e8bbe3000f Mon Sep 17 00:00:00 2001 From: Ievgenii Gryshkun Date: Tue, 19 Feb 2019 11:37:16 +0200 Subject: [PATCH 029/108] Fix message when maxSaleQty is set and qty is more than maxSaleQty --- .../Quote/AddSimpleProductToCartTest.php | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php index 26c84fa0559e7..c877ba555039f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php @@ -67,18 +67,15 @@ public function testAddSimpleProductsToCart() /** * @magentoApiDataFixture Magento/Catalog/_files/products.php - * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php * @expectedException \Exception - * @expectedExceptionMessage The most you may purchase is 5. + * @expectedExceptionMessage The requested qty is not available */ - public function testAddMoreProductsThatAllowed() + public function testAddProductIfQuantityIsNotAvailable() { $sku = 'simple'; - $qty = 7; - $maxQty = 5; + $qty = 200; - $this->config->saveConfig('cataloginventory/item_options/max_sale_qty', $maxQty, 'default', 0); $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getQueryAddSimpleProduct($maskedQuoteId, $sku, $qty); $this->graphQlQuery($query); @@ -87,14 +84,15 @@ public function testAddMoreProductsThatAllowed() /** * @magentoApiDataFixture Magento/Catalog/_files/products.php * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php - * @expectedException \Exception - * @expectedExceptionMessage The requested qty is not available + * @expectedExceptionMessage The most you may purchase is 5. */ - public function testAddProductIfQuantityIsNotAvailable() + public function testAddMoreProductsThatAllowed() { - $sku = 'simple'; - $qty = 200; + $sku = 'custom-design-simple-product'; + $qty = 7; + $maxQty = 5; + $this->config->saveConfig('cataloginventory/item_options/max_sale_qty', $maxQty, 'default', 0); $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getQueryAddSimpleProduct($maskedQuoteId, $sku, $qty); $this->graphQlQuery($query); @@ -102,7 +100,6 @@ public function testAddProductIfQuantityIsNotAvailable() /** * @return string - * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getMaskedQuoteId() : string { From c7930eb37b29c3bc14735d048845278092fca053 Mon Sep 17 00:00:00 2001 From: Yevhenii Dumskyi Date: Tue, 19 Feb 2019 18:07:10 +0200 Subject: [PATCH 030/108] ENGCOM-21154: Add watermark processing in media application --- .../MediaStorage/Service/ImageResize.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/code/Magento/MediaStorage/Service/ImageResize.php b/app/code/Magento/MediaStorage/Service/ImageResize.php index 6e3929296e252..faefd40279084 100644 --- a/app/code/Magento/MediaStorage/Service/ImageResize.php +++ b/app/code/Magento/MediaStorage/Service/ImageResize.php @@ -257,9 +257,41 @@ private function resize(array $viewImage, string $originalImagePath, string $ori ] ); + if (isset($imageParams['watermark_file'])) { + if ($imageParams['watermark_height'] !== null) { + $image->setWatermarkHeight($imageParams['watermark_height']); + } + + if ($imageParams['watermark_width'] !== null) { + $image->setWatermarkWidth($imageParams['watermark_width']); + } + + if ($imageParams['watermark_position'] !== null) { + $image->setWatermarkPosition($imageParams['watermark_position']); + } + + if ($imageParams['watermark_image_opacity'] !== null) { + $image->setWatermarkImageOpacity($imageParams['watermark_image_opacity']); + } + + $image->watermark($this->getWatermarkFilePath($imageParams['watermark_file'])); + } + if ($imageParams['image_width'] !== null && $imageParams['image_height'] !== null) { $image->resize($imageParams['image_width'], $imageParams['image_height']); } $image->save($imageAsset->getPath()); } + + /** + * Returns watermark file absolute path + * + * @param string $file + * @return string + */ + private function getWatermarkFilePath($file) + { + $path = $this->imageConfig->getMediaPath('/watermark/' . $file); + return $this->mediaDirectory->getAbsolutePath($path); + } } From b8554385f4aed658d6cc43213beadbc49f8f317e Mon Sep 17 00:00:00 2001 From: Vitaliy Honcharenko Date: Tue, 19 Feb 2019 11:31:47 -0600 Subject: [PATCH 031/108] MAGETWO-95294: Mysql search slow on the catalog page --- .../Model/ResourceModel/Fulltext/Collection.php | 10 ++-------- .../Test/Unit/SearchAdapter/Query/Builder/SortTest.php | 1 + 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index a978465682527..7cfb59c1bdef6 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -568,11 +568,8 @@ public function getFacetedData($field) public function addCategoryFilter(\Magento\Catalog\Model\Category $category) { $this->addFieldToFilter('category_ids', $category->getId()); - if ($this->isCurrentEngineMysql()) { - parent::addCategoryFilter($category); - } else { - $this->_productLimitationPrice(); - } + $this->_productLimitationPrice(); + return $this; } @@ -585,9 +582,6 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) public function setVisibility($visibility) { $this->addFieldToFilter('visibility', $visibility); - if ($this->isCurrentEngineMysql()) { - parent::setVisibility($visibility); - } return $this; } diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php index aaebd162590f9..efd9073694129 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php @@ -121,6 +121,7 @@ function ($attribute, $context) use ($fieldName) { } /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @return array */ public function getSortProvider() From 1e86f88082360477a56d3beff6fd3d5c877ddaab Mon Sep 17 00:00:00 2001 From: nmalevanec Date: Wed, 20 Feb 2019 11:19:00 +0200 Subject: [PATCH 032/108] Fix static test. --- app/code/Magento/GoogleAnalytics/Block/Ga.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/GoogleAnalytics/Block/Ga.php b/app/code/Magento/GoogleAnalytics/Block/Ga.php index ac178d26cd490..5e6251f7faaf7 100644 --- a/app/code/Magento/GoogleAnalytics/Block/Ga.php +++ b/app/code/Magento/GoogleAnalytics/Block/Ga.php @@ -75,7 +75,8 @@ public function getPageName() } /** - * Render regular page tracking javascript code + * Render regular page tracking javascript code. + * * The custom "page name" may be set from layout or somewhere else. It must start from slash. * * @param string $accountId From 4a598e785441304bef5c7e2817ef6d99a963aca2 Mon Sep 17 00:00:00 2001 From: Jitheesh Date: Wed, 20 Feb 2019 16:14:06 +0530 Subject: [PATCH 033/108] Checkout Page Cancel button is not working #21327 - hide cancel button if billing address is not available on current quote --- .../Checkout/view/frontend/web/js/view/billing-address.js | 6 ++++++ .../view/frontend/web/template/billing-address.html | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js index 6f9a1a46826da..121a94a14852f 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js @@ -201,6 +201,12 @@ function ( this.isAddressDetailsVisible(true); } }, + /** + * Manage cancel button visibility + */ + canUseCancelBillingAddress: ko.computed(function () { + return quote.billingAddress() || lastSelectedBillingAddress; + }), /** * Restore billing address diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html index 5f735fbb4daa9..63edb5057b933 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html @@ -22,7 +22,7 @@ - From d5e2cd081676f747c0ba7871089eaa535409479a Mon Sep 17 00:00:00 2001 From: Lusine Papyan Date: Wed, 20 Feb 2019 16:06:18 +0400 Subject: [PATCH 034/108] MAGETWO-57337: Store View (language) switch leads to 404 - Update automated test script --- .../StoreViewLanguageCorrectSwitchTest.xml | 19 +++++++++++++------ .../StorefrontSwitchStoreViewActionGroup.xml | 5 +++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml index 65fabfe25e817..2c351a12af72e 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml @@ -11,13 +11,12 @@ - - <description value="Check that Store View(language) switches correct"/> <severity value="MAJOR"/> <testCaseId value="MAGETWO-96388"/> <useCaseId value="MAGETWO-57337"/> + <group value="Cms"/> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> @@ -37,7 +36,7 @@ <!-- Create StoreView --> <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> - <argument name="customStore" value="NewStoreViewData"/> + <argument name="customStore" value="NewStoreViewData"/> </actionGroup> <!-- Add StoreView To Cms Page--> @@ -51,10 +50,18 @@ <see userInput="$$createFirstCmsPage.title$$" stepKey="seePageTitle"/> <!-- Switch StoreView and check that Cms Page is open --> - <click selector="{{StorefrontHeaderSection.storeViewSwitcher}}" stepKey="clickStoreViewSwitcher"/> - <waitForElementVisible selector="{{StorefrontHeaderSection.storeViewDropdown}}" stepKey="waitForStoreViewDropDown"/> - <click selector="{{StorefrontHeaderSection.storeViewOption(NewStoreViewData.code)}}" stepKey="selectStoreView"/> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchToCustomStoreView"> + <argument name="storeView" value="NewStoreViewData"/> + </actionGroup> <amOnPage url="{{StorefrontHomePage.url}}/$$createSecondCmsPage.identifier$$" stepKey="gotToSecondCmsPage"/> <see userInput="$$createSecondCmsPage.title$$" stepKey="seePageTitle1"/> + + <!--Open first Cms page on custom store view--> + <amOnPage url="{{StorefrontHomePage.url}}/$$createFirstCmsPage.identifier$$" stepKey="gotToFirstCmsPage1"/> + <see userInput="Whoops, our bad..." stepKey="seePageError"/> + + <!--Switch to default store view and check Cms page--> + <actionGroup ref="StorefrontSwitchDefaultStoreViewActionGroup" stepKey="switchToDefualtStoreView"/> + <see userInput="$$createFirstCmsPage.title$$" stepKey="seePageTitle2"/> </test> </tests> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml index efd3a8c6b8cad..d30fc1e5a2a35 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml @@ -17,4 +17,9 @@ <click selector="{{StorefrontHeaderSection.storeViewOption(storeView.code)}}" stepKey="clickSelectStoreView"/> <waitForPageLoad stepKey="waitForPageLoad"/> </actionGroup> + + <actionGroup name="StorefrontSwitchDefaultStoreViewActionGroup" extends="StorefrontSwitchStoreViewActionGroup"> + <remove keyForRemoval="clickSelectStoreView"/> + <click selector="{{StorefrontHeaderSection.storeViewOption('default')}}" stepKey="clickSelectDefaultStoreView" after="waitForStoreViewDropdown"/> + </actionGroup> </actionGroups> From a0be69b48ad0ac15834c31787d8a4fbc9c4b0ab1 Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Wed, 20 Feb 2019 15:41:36 +0200 Subject: [PATCH 035/108] Fixing the Products grid with default values on multi stores --- .../Ui/DataProvider/Product/ProductDataProvider.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php index 200ecf89641fa..2971a21b964b0 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php @@ -7,6 +7,7 @@ use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\Framework\App\ObjectManager; +use Magento\Store\Model\Store; use Magento\Ui\DataProvider\Modifier\ModifierInterface; use Magento\Ui\DataProvider\Modifier\PoolInterface; @@ -67,6 +68,7 @@ public function __construct( $this->addFieldStrategies = $addFieldStrategies; $this->addFilterStrategies = $addFilterStrategies; $this->modifiersPool = $modifiersPool ?: ObjectManager::getInstance()->get(PoolInterface::class); + $this->setDefaultStoreToCollection(); } /** @@ -140,4 +142,12 @@ public function getMeta() return $meta; } + + /** + * Filter the product collection by Default Store if no filter is applied + */ + private function setDefaultStoreToCollection() + { + $this->getCollection()->setStoreId(Store::DEFAULT_STORE_ID); + } } From 1b6d64c316251604228c32667235b1df9467e0b8 Mon Sep 17 00:00:00 2001 From: Iurii Ivashchenko <iivashchenko@magento.com> Date: Tue, 19 Feb 2019 14:52:28 +0200 Subject: [PATCH 036/108] MAGETWO-98028: Shipping tax rounding issue --- .../Magento/SalesRule/Model/Validator.php | 22 +-- .../Test/Unit/Model/ValidatorTest.php | 148 ++++++++++-------- .../ui_component/sales_rule_form.xml | 2 +- .../web/js/form/element/apply_to_shipping.js | 37 +++++ .../TestCase/CreateSalesRuleEntityTest.xml | 1 - 5 files changed, 131 insertions(+), 79 deletions(-) create mode 100644 app/code/Magento/SalesRule/view/adminhtml/web/js/form/element/apply_to_shipping.js diff --git a/app/code/Magento/SalesRule/Model/Validator.php b/app/code/Magento/SalesRule/Model/Validator.php index 5c0f97ae0b08b..ea0221d8f072d 100644 --- a/app/code/Magento/SalesRule/Model/Validator.php +++ b/app/code/Magento/SalesRule/Model/Validator.php @@ -182,6 +182,8 @@ protected function _getRules(Address $address = null) } /** + * Address id getter. + * * @param Address $address * @return string */ @@ -327,21 +329,7 @@ public function processShippingAmount(Address $address) $baseDiscountAmount = $rule->getDiscountAmount(); break; case \Magento\SalesRule\Model\Rule::CART_FIXED_ACTION: - $cartRules = $address->getCartFixedRules(); - if (!isset($cartRules[$rule->getId()])) { - $cartRules[$rule->getId()] = $rule->getDiscountAmount(); - } - if ($cartRules[$rule->getId()] > 0) { - $quoteAmount = $this->priceCurrency->convert($cartRules[$rule->getId()], $quote->getStore()); - $discountAmount = min($shippingAmount - $address->getShippingDiscountAmount(), $quoteAmount); - $baseDiscountAmount = min( - $baseShippingAmount - $address->getBaseShippingDiscountAmount(), - $cartRules[$rule->getId()] - ); - $cartRules[$rule->getId()] -= $baseDiscountAmount; - } - - $address->setCartFixedRules($cartRules); + // Shouldn't be proceed according to MAGETWO-96403 break; } @@ -519,6 +507,8 @@ public function sortItemsByPriority($items, Address $address = null) } /** + * Rule total items getter. + * * @param int $key * @return array * @throws \Magento\Framework\Exception\LocalizedException @@ -533,6 +523,8 @@ public function getRuleItemTotalsInfo($key) } /** + * Decrease rule items count. + * * @param int $key * @return $this */ diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/ValidatorTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/ValidatorTest.php index 42448565791c5..e86068946ca78 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/ValidatorTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/ValidatorTest.php @@ -5,6 +5,13 @@ */ namespace Magento\SalesRule\Test\Unit\Model; +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Quote\Model\Quote; +use Magento\SalesRule\Model\Rule; +use Magento\SalesRule\Model\Validator; +use Magento\Store\Model\Store; +use PHPUnit\Framework\MockObject\MockObject; + /** * Class ValidatorTest * @@SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -17,50 +24,55 @@ class ValidatorTest extends \PHPUnit\Framework\TestCase protected $helper; /** - * @var \Magento\SalesRule\Model\Validator + * @var Validator */ protected $model; /** - * @var \Magento\Quote\Model\Quote\Item|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Quote\Model\Quote\Item|MockObject */ protected $item; /** - * @var \Magento\Quote\Model\Quote\Address|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Quote\Model\Quote\Address|MockObject */ protected $addressMock; /** - * @var \Magento\SalesRule\Model\RulesApplier|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\SalesRule\Model\RulesApplier|MockObject */ protected $rulesApplier; /** - * @var \Magento\SalesRule\Model\Validator\Pool|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\SalesRule\Model\Validator\Pool|MockObject */ protected $validators; /** - * @var \Magento\SalesRule\Model\Utility|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\SalesRule\Model\Utility|MockObject */ protected $utility; /** - * @var \Magento\SalesRule\Model\ResourceModel\Rule\Collection|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\SalesRule\Model\ResourceModel\Rule\Collection|MockObject */ protected $ruleCollection; /** - * @var \Magento\Catalog\Helper\Data|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Catalog\Helper\Data|MockObject */ protected $catalogData; /** - * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Message\ManagerInterface|MockObject */ protected $messageManager; + /** + * @var PriceCurrencyInterface|MockObject + */ + private $priceCurrency; + protected function setUp() { $this->helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -74,6 +86,7 @@ protected function setUp() ->setMethods( [ 'getShippingAmountForDiscount', + 'getBaseShippingAmountForDiscount', 'getQuote', 'getCustomAttributesCodes', 'setCartFixedRules' @@ -81,7 +94,7 @@ protected function setUp() ) ->getMock(); - /** @var \Magento\Quote\Model\Quote\Item\AbstractItem|\PHPUnit_Framework_MockObject_MockObject $item */ + /** @var \Magento\Quote\Model\Quote\Item\AbstractItem|MockObject $item */ $this->item = $this->createPartialMock( \Magento\Quote\Model\Quote\Item::class, ['__wakeup', 'getAddress', 'getParentItemId'] @@ -100,10 +113,13 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $ruleCollectionFactoryMock = $this->prepareRuleCollectionMock($this->ruleCollection); + $this->priceCurrency = $this->getMockBuilder(PriceCurrencyInterface::class) + ->disableOriginalConstructor() + ->getMock(); - /** @var \Magento\SalesRule\Model\Validator|\PHPUnit_Framework_MockObject_MockObject $validator */ + /** @var Validator|MockObject $validator */ $this->model = $this->helper->getObject( - \Magento\SalesRule\Model\Validator::class, + Validator::class, [ 'context' => $context, 'registry' => $registry, @@ -112,7 +128,8 @@ protected function setUp() 'utility' => $this->utility, 'rulesApplier' => $this->rulesApplier, 'validators' => $this->validators, - 'messageManager' => $this->messageManager + 'messageManager' => $this->messageManager, + 'priceCurrency' => $this->priceCurrency ] ); $this->model->setWebsiteId(1); @@ -131,7 +148,7 @@ protected function setUp() } /** - * @return \Magento\Quote\Model\Quote\Item|\PHPUnit_Framework_MockObject_MockObject + * @return \Magento\Quote\Model\Quote\Item|MockObject */ protected function getQuoteItemMock() { @@ -145,8 +162,8 @@ protected function getQuoteItemMock() $itemSimple = $this->createPartialMock(\Magento\Quote\Model\Quote\Item::class, ['getAddress', '__wakeup']); $itemSimple->expects($this->any())->method('getAddress')->will($this->returnValue($this->addressMock)); - /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->createPartialMock(\Magento\Quote\Model\Quote::class, ['getStoreId', '__wakeup']); + /** @var $quote Quote */ + $quote = $this->createPartialMock(Quote::class, ['getStoreId', '__wakeup']); $quote->expects($this->any())->method('getStoreId')->will($this->returnValue(1)); $itemData = include $fixturePath . 'quote_item_downloadable.php'; @@ -168,7 +185,7 @@ public function testCanApplyRules() $this->model->getCouponCode() ); $item = $this->getQuoteItemMock(); - $rule = $this->createMock(\Magento\SalesRule\Model\Rule::class); + $rule = $this->createMock(Rule::class); $actionsCollection = $this->createPartialMock(\Magento\Rule\Model\Action\Collection::class, ['validate']); $actionsCollection->expects($this->any()) ->method('validate') @@ -278,7 +295,7 @@ public function testApplyRulesThatAppliedRuleIdsAreCollected() public function testInit() { $this->assertInstanceOf( - \Magento\SalesRule\Model\Validator::class, + Validator::class, $this->model->init( $this->model->getWebsiteId(), $this->model->getCustomerGroupId(), @@ -314,7 +331,7 @@ public function testCanApplyDiscount() public function testInitTotalsCanApplyDiscount() { $rule = $this->createPartialMock( - \Magento\SalesRule\Model\Rule::class, + Rule::class, ['getSimpleAction', 'getActions', 'getId'] ); $item1 = $this->getMockForAbstractClass( @@ -337,7 +354,7 @@ public function testInitTotalsCanApplyDiscount() $rule->expects($this->any()) ->method('getSimpleAction') - ->willReturn(\Magento\SalesRule\Model\Rule::CART_FIXED_ACTION); + ->willReturn(Rule::CART_FIXED_ACTION); $iterator = new \ArrayIterator([$rule]); $this->ruleCollection->expects($this->once())->method('getIterator')->willReturn($iterator); $validator = $this->getMockBuilder(\Magento\Framework\Validator\AbstractValidator::class) @@ -392,7 +409,7 @@ public function testInitTotalsNoItems() /** * @param $ruleCollection - * @return \PHPUnit_Framework_MockObject_MockObject + * @return MockObject */ protected function prepareRuleCollectionMock($ruleCollection) { @@ -427,14 +444,14 @@ public function testProcessShippingAmountNoRules() $this->model->getCouponCode() ); $this->assertInstanceOf( - \Magento\SalesRule\Model\Validator::class, + Validator::class, $this->model->processShippingAmount($this->setupAddressMock()) ); } public function testProcessShippingAmountProcessDisabled() { - $ruleMock = $this->getMockBuilder(\Magento\SalesRule\Model\Rule::class) + $ruleMock = $this->getMockBuilder(Rule::class) ->disableOriginalConstructor() ->setMethods([]) ->getMock(); @@ -448,51 +465,54 @@ public function testProcessShippingAmountProcessDisabled() $this->model->getCouponCode() ); $this->assertInstanceOf( - \Magento\SalesRule\Model\Validator::class, + Validator::class, $this->model->processShippingAmount($this->setupAddressMock()) ); } /** + * Tests shipping amounts according to rule simple action. + * * @param string $action + * @param int $ruleDiscount + * @param int $shippingDiscount * @dataProvider dataProviderActions */ - public function testProcessShippingAmountActions($action) + public function testProcessShippingAmountActions($action, $ruleDiscount, $shippingDiscount): void { - $discountAmount = 50; + $shippingAmount = 5; - $ruleMock = $this->getMockBuilder(\Magento\SalesRule\Model\Rule::class) + $ruleMock = $this->getMockBuilder(Rule::class) ->disableOriginalConstructor() ->setMethods(['getApplyToShipping', 'getSimpleAction', 'getDiscountAmount']) ->getMock(); - $ruleMock->expects($this->any()) - ->method('getApplyToShipping') + $ruleMock->method('getApplyToShipping') ->willReturn(true); - $ruleMock->expects($this->any()) - ->method('getDiscountAmount') - ->willReturn($discountAmount); - $ruleMock->expects($this->any()) - ->method('getSimpleAction') + $ruleMock->method('getDiscountAmount') + ->willReturn($ruleDiscount); + $ruleMock->method('getSimpleAction') ->willReturn($action); $iterator = new \ArrayIterator([$ruleMock]); - $this->ruleCollection->expects($this->any()) - ->method('getIterator') + $this->ruleCollection->method('getIterator') ->willReturn($iterator); - $this->utility->expects($this->any()) - ->method('canProcessRule') + $this->utility->method('canProcessRule') ->willReturn(true); + $this->priceCurrency->method('convert') + ->willReturn($ruleDiscount); + $this->model->init( $this->model->getWebsiteId(), $this->model->getCustomerGroupId(), $this->model->getCouponCode() ); - $this->assertInstanceOf( - \Magento\SalesRule\Model\Validator::class, - $this->model->processShippingAmount($this->setupAddressMock(5)) - ); + + $addressMock = $this->setupAddressMock($shippingAmount); + + self::assertInstanceOf(Validator::class, $this->model->processShippingAmount($addressMock)); + self::assertEquals($shippingDiscount, $addressMock->getShippingDiscountAmount()); } /** @@ -501,44 +521,48 @@ public function testProcessShippingAmountActions($action) public static function dataProviderActions() { return [ - [\Magento\SalesRule\Model\Rule::TO_PERCENT_ACTION], - [\Magento\SalesRule\Model\Rule::BY_PERCENT_ACTION], - [\Magento\SalesRule\Model\Rule::TO_FIXED_ACTION], - [\Magento\SalesRule\Model\Rule::BY_FIXED_ACTION], - [\Magento\SalesRule\Model\Rule::CART_FIXED_ACTION], + [Rule::TO_PERCENT_ACTION, 50, 2.5], + [Rule::BY_PERCENT_ACTION, 50, 2.5], + [Rule::TO_FIXED_ACTION, 5, 0], + [Rule::BY_FIXED_ACTION, 5, 5], + [Rule::CART_FIXED_ACTION, 5, 0], ]; } /** * @param null|int $shippingAmount - * @return \PHPUnit_Framework_MockObject_MockObject + * @return MockObject */ protected function setupAddressMock($shippingAmount = null) { - $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + $storeMock = $this->getMockBuilder(Store::class) ->disableOriginalConstructor() ->setMethods([]) ->getMock(); - $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class) + + $quoteMock = $this->getMockBuilder(Quote::class) ->disableOriginalConstructor() ->setMethods(['setAppliedRuleIds', 'getStore']) ->getMock(); - $quoteMock->expects($this->any()) - ->method('getStore') + + $quoteMock->method('getStore') ->willReturn($storeMock); - $quoteMock->expects($this->any()) - ->method('setAppliedRuleIds') + + $quoteMock->method('setAppliedRuleIds') ->willReturnSelf(); - $this->addressMock->expects($this->any()) - ->method('getShippingAmountForDiscount') + $this->addressMock->method('getShippingAmountForDiscount') ->willReturn($shippingAmount); - $this->addressMock->expects($this->any()) - ->method('getQuote') + + $this->addressMock->method('getBaseShippingAmountForDiscount') + ->willReturn($shippingAmount); + + $this->addressMock->method('getQuote') ->willReturn($quoteMock); - $this->addressMock->expects($this->any()) - ->method('getCustomAttributesCodes') + + $this->addressMock->method('getCustomAttributesCodes') ->willReturn([]); + return $this->addressMock; } @@ -546,7 +570,7 @@ public function testReset() { $this->utility->expects($this->once()) ->method('resetRoundingDeltas'); - $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class) + $quoteMock = $this->getMockBuilder(Quote::class) ->disableOriginalConstructor() ->getMock(); $addressMock = $this->getMockBuilder(\Magento\Quote\Model\Quote\Address::class) @@ -560,6 +584,6 @@ public function testReset() $this->model->getCustomerGroupId(), $this->model->getCouponCode() ); - $this->assertInstanceOf(\Magento\SalesRule\Model\Validator::class, $this->model->reset($addressMock)); + $this->assertInstanceOf(Validator::class, $this->model->reset($addressMock)); } } diff --git a/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml b/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml index 9b579f47759a6..570eb0bf151f0 100644 --- a/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml +++ b/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml @@ -452,7 +452,7 @@ <dataScope>discount_step</dataScope> </settings> </field> - <field name="apply_to_shipping" component="Magento_Ui/js/form/element/single-checkbox-toggle-notice" formElement="checkbox"> + <field name="apply_to_shipping" component="Magento_SalesRule/js/form/element/apply_to_shipping" formElement="checkbox"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="source" xsi:type="string">sales_rule</item> diff --git a/app/code/Magento/SalesRule/view/adminhtml/web/js/form/element/apply_to_shipping.js b/app/code/Magento/SalesRule/view/adminhtml/web/js/form/element/apply_to_shipping.js new file mode 100644 index 0000000000000..dfb3f909345b3 --- /dev/null +++ b/app/code/Magento/SalesRule/view/adminhtml/web/js/form/element/apply_to_shipping.js @@ -0,0 +1,37 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/element/single-checkbox-toggle-notice' +], function (Checkbox) { + 'use strict'; + + return Checkbox.extend({ + defaults: { + imports: { + toggleDisabled: '${ $.parentName }.simple_action:value' + } + }, + + /** + * Toggle element disabled state according to simple action value. + * + * @param {String} action + */ + toggleDisabled: function (action) { + switch (action) { + case 'cart_fixed': + this.disabled(true); + break; + default: + this.disabled(false); + } + + if (this.disabled()) { + this.checked(false); + } + } + }); +}); diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml index 586ad2acee203..4995c1feb048e 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml @@ -75,7 +75,6 @@ <data name="salesRule/data/coupon_code" xsi:type="string">Lorem ipsum dolor sit amet, consectetur adipiscing elit - %isolation%</data> <data name="salesRule/data/simple_action" xsi:type="string">Fixed amount discount for whole cart</data> <data name="salesRule/data/discount_amount" xsi:type="string">60</data> - <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> <data name="salesRule/data/store_labels/0" xsi:type="string">Coupon code+Fixed amount discount for whole cart</data> <data name="productForSalesRule1/dataset" xsi:type="string">simple_for_salesrule_1</data> From 9e657ad9a2696e15a346d636a245c9a2184e9019 Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Wed, 20 Feb 2019 17:20:13 +0200 Subject: [PATCH 037/108] Moving the method's logic into construct --- .../Ui/DataProvider/Product/ProductDataProvider.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php index 2971a21b964b0..edc2bcb66f7db 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php @@ -68,7 +68,7 @@ public function __construct( $this->addFieldStrategies = $addFieldStrategies; $this->addFilterStrategies = $addFilterStrategies; $this->modifiersPool = $modifiersPool ?: ObjectManager::getInstance()->get(PoolInterface::class); - $this->setDefaultStoreToCollection(); + $this->getCollection()->setStoreId(Store::DEFAULT_STORE_ID); } /** @@ -142,12 +142,4 @@ public function getMeta() return $meta; } - - /** - * Filter the product collection by Default Store if no filter is applied - */ - private function setDefaultStoreToCollection() - { - $this->getCollection()->setStoreId(Store::DEFAULT_STORE_ID); - } } From 67ea734015ceab3aa05f740a55e5689450e3f1cd Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Wed, 20 Feb 2019 22:22:24 +0200 Subject: [PATCH 038/108] Fix issue 19983 --- app/code/Magento/GoogleAnalytics/Block/Ga.php | 2 +- app/code/Magento/GoogleAnalytics/Helper/Data.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/GoogleAnalytics/Block/Ga.php b/app/code/Magento/GoogleAnalytics/Block/Ga.php index 5e6251f7faaf7..b5917407b60ae 100644 --- a/app/code/Magento/GoogleAnalytics/Block/Ga.php +++ b/app/code/Magento/GoogleAnalytics/Block/Ga.php @@ -206,7 +206,7 @@ public function getPageTrackingData($accountId) { return [ 'optPageUrl' => $this->getOptPageUrl(), - 'isAnonymizedIpActive' => (int)$this->_googleAnalyticsData->isAnonymizedIpActive(), + 'isAnonymizedIpActive' => $this->_googleAnalyticsData->isAnonymizedIpActive(), 'accountId' => $this->escapeHtmlAttr($accountId, false) ]; } diff --git a/app/code/Magento/GoogleAnalytics/Helper/Data.php b/app/code/Magento/GoogleAnalytics/Helper/Data.php index 2af03c71fb1b0..90a207921d51f 100644 --- a/app/code/Magento/GoogleAnalytics/Helper/Data.php +++ b/app/code/Magento/GoogleAnalytics/Helper/Data.php @@ -46,6 +46,6 @@ public function isGoogleAnalyticsAvailable($store = null) */ public function isAnonymizedIpActive($store = null) { - return $this->scopeConfig->getValue(self::XML_PATH_ANONYMIZE, ScopeInterface::SCOPE_STORE, $store); + return (bool)$this->scopeConfig->getValue(self::XML_PATH_ANONYMIZE, ScopeInterface::SCOPE_STORE, $store); } } From 139d4fa0676397aded13f512c28560655a31aca6 Mon Sep 17 00:00:00 2001 From: Nathan Smith <nathsmit@adobe.com> Date: Wed, 20 Feb 2019 15:10:43 -0600 Subject: [PATCH 039/108] MC-15020: Customer attribute of type file causes customer page to not load - Declared hidden dependency --- .../Magento/Ui/view/base/web/js/form/element/file-uploader.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js index 357571350a268..c6bf36f5cc867 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js @@ -16,7 +16,8 @@ define([ 'Magento_Ui/js/form/element/abstract', 'mage/backend/notification', 'mage/translate', - 'jquery/file-uploader' + 'jquery/file-uploader', + 'mage/adminhtml/tools' ], function ($, _, utils, uiAlert, validator, Element, notification, $t) { 'use strict'; From 5372fdc88a4aa1e00d9983b7f538760d2f210fe1 Mon Sep 17 00:00:00 2001 From: Jitheesh <jitheeshvo@gmail.com> Date: Thu, 21 Feb 2019 10:28:32 +0530 Subject: [PATCH 040/108] Checkout Page Cancel button is not working #21327 - CR fix --- .../Checkout/view/frontend/web/js/view/billing-address.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js index 121a94a14852f..4ea6fb5e5df75 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js @@ -201,6 +201,7 @@ function ( this.isAddressDetailsVisible(true); } }, + /** * Manage cancel button visibility */ From d10e5060778be9ce6fbcb9e4a40ff8b410e55dbb Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza <enarc@atwix.com> Date: Wed, 20 Feb 2019 15:31:08 +0100 Subject: [PATCH 041/108] New schema for setting shipping methods --- .../ConfigurableProductGraphQl/etc/module.xml | 1 + .../Model/Cart/ExtractDataFromAddress.php | 10 +- .../Magento/QuoteGraphQl/etc/schema.graphqls | 16 +- .../Quote/SetShippingMethodsOnCartTest.php | 333 ++++++++++++++++++ 4 files changed, 356 insertions(+), 4 deletions(-) create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml index 98e7957d0af8e..f249a417f1046 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml @@ -12,6 +12,7 @@ <module name="Magento_ConfigurableProduct"/> <module name="Magento_GraphQl"/> <module name="Magento_CatalogGraphQl"/> + <module name="Magento_QuoteGraphQl"/> </sequence> </module> </config> diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php index b0e5070315d87..9b27fd8df6c82 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php @@ -40,6 +40,11 @@ public function execute(QuoteAddress $address): array $addressData = $this->dataObjectConverter->toFlatArray($address, [], AddressInterface::class); $addressData['model'] = $address; + if ($address->getShippingMethod()) { + list($carrierCode, $methodCode) = explode('_', $address->getShippingMethod(), 2); + $shippingAmount = $address->getShippingAmount(); + } + $addressData = array_merge($addressData, [ 'country' => [ 'code' => $address->getCountryId(), @@ -51,9 +56,10 @@ public function execute(QuoteAddress $address): array ], 'street' => $address->getStreet(), 'selected_shipping_method' => [ - 'code' => $address->getShippingMethod(), + 'carrier_code' => $carrierCode ?? null, + 'method_code' => $methodCode ?? null, 'label' => $address->getShippingDescription(), - 'free_shipping' => $address->getFreeShipping(), + 'amount' => $shippingAmount ?? null ], 'items_weight' => $address->getWeight(), 'customer_notes' => $address->getCustomerNotes() diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index a62e92ae0e76b..8fe66da3609f8 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -60,11 +60,15 @@ input CartAddressInput { input SetShippingMethodsOnCartInput { cart_id: String! - shipping_methods: [ShippingMethodForAddressInput!]! + shipping_addresses: [ShippingMethodForAddressInput!]! } input ShippingMethodForAddressInput { cart_address_id: Int! + shipping_method: ShippingMethodInput! +} + +input ShippingMethodInput { carrier_code: String! method_code: String! } @@ -140,7 +144,10 @@ type CartAddressCountry { } type SelectedShippingMethod { - amount: Float! + carrier_code: String + method_code: String + label: String + amount: Float } type AvailableShippingMethod { @@ -246,3 +253,8 @@ type CartItemSelectedOptionValuePrice { units: String! type: PriceTypeEnum! } + +input CartItemDetailsInput { + sku: String! + qty: Float! +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php new file mode 100644 index 0000000000000..7aea8093e88a0 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php @@ -0,0 +1,333 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote; + +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for setting shipping methods on cart + */ +class SetShippingMethodsOnCartTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var Quote + */ + private $quote; + + /** + * @var QuoteIdToMaskedQuoteIdInterface + */ + private $quoteIdToMaskedId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->quoteResource = $objectManager->create(QuoteResource::class); + $this->quote = $objectManager->create(Quote::class); + $this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * Test for general routine of setting a shipping method on shopping cart + * + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + */ + public function testSetShippingMethodOnCart() + { + $shippingCarrierCode = 'flatrate'; + $shippingMethodCode = 'flatrate'; + $this->quoteResource->load( + $this->quote, + 'test_order_1', + 'reserved_order_id' + ); + $shippingAddress = $this->quote->getShippingAddress(); + $shippingAddressId = $shippingAddress->getId(); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + + $query = $this->prepareMutationQuery( + $maskedQuoteId, + $shippingMethodCode, + $shippingCarrierCode, + $shippingAddressId + ); + + $response = $this->sendRequestWithToken($query); + + self::assertArrayHasKey('setShippingMethodsOnCart', $response); + self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); + self::assertEquals($maskedQuoteId, $response['setShippingMethodsOnCart']['cart']['cart_id']); + $addressesInformation = $response['setShippingMethodsOnCart']['cart']['shipping_addresses']; + self::assertCount(1, $addressesInformation); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + */ + public function testSetFlatrateOnCart() + { + $this->setShippingMethodAndCheckResponse( + 'flatrate', + 'flatrate', + '10', + 'Flat Rate - Fixed' + ); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + */ + public function testSetTableRatesOnCart() + { + $this->setShippingMethodAndCheckResponse( + 'tablerate', + 'bestway', + '15', + 'Best Way - Table Rate' + ); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + */ + public function testSetFreeShippingOnCart() + { + $this->setShippingMethodAndCheckResponse( + 'freeshipping', + 'freeshipping', + '0', + 'Free Shipping - Free' + ); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + */ + public function testSetShippingMethodWithWrongCartId() + { + $shippingCarrierCode = 'flatrate'; + $shippingMethodCode = 'flatrate'; + $shippingAddressId = '1'; + $maskedQuoteId = 'invalid'; + + $query = $this->prepareMutationQuery( + $maskedQuoteId, + $shippingMethodCode, + $shippingCarrierCode, + $shippingAddressId + ); + + self::expectExceptionMessage("Could not find a cart with ID \"$maskedQuoteId\""); + $this->sendRequestWithToken($query); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + */ + public function testSetNonExistingShippingMethod() + { + $shippingCarrierCode = 'non'; + $shippingMethodCode = 'existing'; + $this->quoteResource->load( + $this->quote, + 'test_order_1', + 'reserved_order_id' + ); + $shippingAddress = $this->quote->getShippingAddress(); + $shippingAddressId = $shippingAddress->getId(); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + + $query = $this->prepareMutationQuery( + $maskedQuoteId, + $shippingMethodCode, + $shippingCarrierCode, + $shippingAddressId + ); + + self::expectExceptionMessage("Carrier with such method not found: $shippingCarrierCode, $shippingMethodCode"); + $this->sendRequestWithToken($query); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + */ + public function testSetShippingMethodWithNonExistingAddress() + { + $shippingCarrierCode = 'flatrate'; + $shippingMethodCode = 'flatrate'; + $this->quoteResource->load( + $this->quote, + 'test_order_1', + 'reserved_order_id' + ); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $shippingAddressId = '-20'; + + $query = $this->prepareMutationQuery( + $maskedQuoteId, + $shippingMethodCode, + $shippingCarrierCode, + $shippingAddressId + ); + + self::expectExceptionMessage('The shipping address is missing. Set the address and try again.'); + $this->sendRequestWithToken($query); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + */ + public function testSetShippingMethodByGuestToCustomerCart() + { + $shippingCarrierCode = 'flatrate'; + $shippingMethodCode = 'flatrate'; + $this->quoteResource->load( + $this->quote, + 'test_order_1', + 'reserved_order_id' + ); + $shippingAddress = $this->quote->getShippingAddress(); + $shippingAddressId = $shippingAddress->getId(); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + + $query = $this->prepareMutationQuery( + $maskedQuoteId, + $shippingMethodCode, + $shippingCarrierCode, + $shippingAddressId + ); + + self::expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + + $this->graphQlQuery($query); + } + + /** + * Send request for setting the requested shipping method and check the output + * + * @param string $shippingCarrierCode + * @param string $shippingMethodCode + * @param string $shippingAmount + * @param string $shippingLabel + * @throws \Magento\Framework\Exception\AuthenticationException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function setShippingMethodAndCheckResponse( + string $shippingCarrierCode, + string $shippingMethodCode, + string $shippingAmount, + string $shippingLabel + ) { + $this->quoteResource->load( + $this->quote, + 'test_order_1', + 'reserved_order_id' + ); + $shippingAddress = $this->quote->getShippingAddress(); + $shippingAddressId = $shippingAddress->getId(); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + + $query = $this->prepareMutationQuery( + $maskedQuoteId, + $shippingMethodCode, + $shippingCarrierCode, + $shippingAddressId + ); + + $response = $this->sendRequestWithToken($query); + + $addressesInformation = $response['setShippingMethodsOnCart']['cart']['shipping_addresses']; + self::assertEquals($addressesInformation[0]['selected_shipping_method']['carrier_code'], $shippingCarrierCode); + self::assertEquals($addressesInformation[0]['selected_shipping_method']['method_code'], $shippingMethodCode); + self::assertEquals($addressesInformation[0]['selected_shipping_method']['amount'], $shippingAmount); + self::assertEquals($addressesInformation[0]['selected_shipping_method']['label'], $shippingLabel); + } + + /** + * Generates query for setting the specified shipping method on cart + * + * @param string $maskedQuoteId + * @param string $shippingMethodCode + * @param string $shippingCarrierCode + * @param string $shippingAddressId + * @return string + */ + private function prepareMutationQuery( + string $maskedQuoteId, + string $shippingMethodCode, + string $shippingCarrierCode, + string $shippingAddressId + ) : string { + return <<<QUERY +mutation { + setShippingMethodsOnCart(input: + { + cart_id: "$maskedQuoteId", + shipping_addresses: [{ + cart_address_id: $shippingAddressId + shipping_method: { + method_code: "$shippingMethodCode" + carrier_code: "$shippingCarrierCode" + } + }] + }) { + + cart { + cart_id, + shipping_addresses { + selected_shipping_method { + carrier_code + method_code + label + amount + } + } + } + } +} + +QUERY; + } + + /** + * Sends a GraphQL request with using a bearer token + * + * @param string $query + * @return array + * @throws \Magento\Framework\Exception\AuthenticationException + */ + private function sendRequestWithToken(string $query): array + { + + $customerToken = $this->customerTokenService->createCustomerAccessToken('customer@example.com', 'password'); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + + return $this->graphQlQuery($query, [], '', $headerMap); + } +} From 72d4183d5aafb2e23b0c1ce34e0ba6ed4efc015d Mon Sep 17 00:00:00 2001 From: Vitaliy Honcharenko <vgoncharenko@magento.com> Date: Thu, 21 Feb 2019 12:38:42 -0600 Subject: [PATCH 042/108] MAGETWO-98369: Improve degradation of CatalogSearch indexation with elasticsearch --- .../Product/FieldProvider/DynamicField.php | 26 +++++++++++---- .../FieldProvider/DynamicFieldTest.php | 33 +++++++++---------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php index 9e2659a757924..7fa460fbb3968 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php @@ -18,6 +18,8 @@ use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface as FieldNameResolver; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Catalog\Model\ResourceModel\Category\Collection; +use Magento\Framework\App\ObjectManager; /** * Provide dynamic fields for product. @@ -27,10 +29,18 @@ class DynamicField implements FieldProviderInterface /** * Category list. * + * @deprecated * @var CategoryListInterface */ private $categoryList; + /** + * Category collection. + * + * @var Collection + */ + private $categoryCollection; + /** * Customer group repository. * @@ -73,6 +83,7 @@ class DynamicField implements FieldProviderInterface * @param CategoryListInterface $categoryList * @param FieldNameResolver $fieldNameResolver * @param AttributeProvider $attributeAdapterProvider + * @param Collection|null $categoryCollection */ public function __construct( FieldTypeConverterInterface $fieldTypeConverter, @@ -81,7 +92,8 @@ public function __construct( SearchCriteriaBuilder $searchCriteriaBuilder, CategoryListInterface $categoryList, FieldNameResolver $fieldNameResolver, - AttributeProvider $attributeAdapterProvider + AttributeProvider $attributeAdapterProvider, + Collection $categoryCollection = null ) { $this->groupRepository = $groupRepository; $this->searchCriteriaBuilder = $searchCriteriaBuilder; @@ -90,6 +102,8 @@ public function __construct( $this->categoryList = $categoryList; $this->fieldNameResolver = $fieldNameResolver; $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->categoryCollection = $categoryCollection ?: + ObjectManager::getInstance()->get(Collection::class); } /** @@ -98,18 +112,17 @@ public function __construct( public function getFields(array $context = []): array { $allAttributes = []; - $searchCriteria = $this->searchCriteriaBuilder->create(); - $categories = $this->categoryList->getList($searchCriteria)->getItems(); + $categoryIds = $this->categoryCollection->getAllIds(); $positionAttribute = $this->attributeAdapterProvider->getByAttributeCode('position'); $categoryNameAttribute = $this->attributeAdapterProvider->getByAttributeCode('category_name'); - foreach ($categories as $category) { + foreach ($categoryIds as $categoryId) { $categoryPositionKey = $this->fieldNameResolver->getFieldName( $positionAttribute, - ['categoryId' => $category->getId()] + ['categoryId' => $categoryId] ); $categoryNameKey = $this->fieldNameResolver->getFieldName( $categoryNameAttribute, - ['categoryId' => $category->getId()] + ['categoryId' => $categoryId] ); $allAttributes[$categoryPositionKey] = [ 'type' => $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_STRING), @@ -121,6 +134,7 @@ public function getFields(array $context = []): array ]; } + $searchCriteria = $this->searchCriteriaBuilder->create(); $groups = $this->groupRepository->getList($searchCriteria)->getItems(); $priceAttribute = $this->attributeAdapterProvider->getByAttributeCode('price'); foreach ($groups as $group) { diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php index ba5e97aa14b54..7c2a33c05aa08 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php @@ -24,6 +24,7 @@ use Magento\Customer\Api\Data\GroupInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface as FieldNameResolver; +use Magento\Catalog\Model\ResourceModel\Category\Collection; /** * @SuppressWarnings(PHPMD) @@ -65,6 +66,11 @@ class DynamicFieldTest extends \PHPUnit\Framework\TestCase */ private $categoryList; + /** + * @var Collection + */ + private $categoryCollection; + /** * @var FieldNameResolver */ @@ -100,6 +106,10 @@ protected function setUp() $this->categoryList = $this->getMockBuilder(CategoryListInterface::class) ->disableOriginalConstructor() ->getMock(); + $this->categoryCollection = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->setMethods(['getAllIds']) + ->getMock(); $objectManager = new ObjectManagerHelper($this); @@ -113,6 +123,7 @@ protected function setUp() 'attributeAdapterProvider' => $this->attributeAdapterProvider, 'categoryList' => $this->categoryList, 'fieldNameResolver' => $this->fieldNameResolver, + 'categoryCollection' => $this->categoryCollection, ] ); } @@ -124,7 +135,6 @@ protected function setUp() * @param $groupId * @param array $expected * @return void - * @throws \Magento\Framework\Exception\LocalizedException */ public function testGetAllAttributesTypes( $complexType, @@ -138,10 +148,6 @@ public function testGetAllAttributesTypes( $this->searchCriteriaBuilder->expects($this->any()) ->method('create') ->willReturn($searchCriteria); - $categorySearchResults = $this->getMockBuilder(CategorySearchResultsInterface::class) - ->disableOriginalConstructor() - ->setMethods(['getItems']) - ->getMockForAbstractClass(); $groupSearchResults = $this->getMockBuilder(GroupSearchResultsInterface::class) ->disableOriginalConstructor() ->setMethods(['getItems']) @@ -156,19 +162,10 @@ public function testGetAllAttributesTypes( $groupSearchResults->expects($this->any()) ->method('getItems') ->willReturn([$group]); - $category = $this->getMockBuilder(CategoryInterface::class) - ->disableOriginalConstructor() - ->setMethods(['getId']) - ->getMockForAbstractClass(); - $category->expects($this->any()) - ->method('getId') - ->willReturn($categoryId); - $categorySearchResults->expects($this->any()) - ->method('getItems') - ->willReturn([$category]); - $this->categoryList->expects($this->any()) - ->method('getList') - ->willReturn($categorySearchResults); + + $this->categoryCollection->expects($this->any()) + ->method('getAllIds') + ->willReturn([$categoryId]); $categoryAttributeMock = $this->getMockBuilder(AttributeAdapter::class) ->disableOriginalConstructor() From 364ebfe5c1092b2c29b6eb5d5f36d2b8edec56a7 Mon Sep 17 00:00:00 2001 From: Nathan Smith <nathsmit@adobe.com> Date: Thu, 21 Feb 2019 15:00:25 -0600 Subject: [PATCH 043/108] MC-15004: Indexer hangs on category/products index - Changed algorithm of batching for affected indexers - Cleaned up touched files --- .../Indexer/Category/Product/Action/Full.php | 142 +++++----- .../Model/Indexer/Product/Eav/Action/Full.php | 112 +++++--- .../Indexer/Product/Price/Action/Full.php | 246 ++++++++++-------- .../Indexer/Product/Eav/Action/FullTest.php | 73 ++++-- .../Model/Indexer/Stock/Action/Full.php | 55 ++-- 5 files changed, 369 insertions(+), 259 deletions(-) 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 f8121b55dbf99..e7b1e2c9bd62f 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 @@ -3,33 +3,46 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Catalog\Model\Indexer\Category\Product\Action; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Config; +use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Query\Generator as QueryGenerator; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Select; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Indexer\BatchProviderInterface; +use Magento\Framework\Indexer\BatchSizeManagementInterface; use Magento\Indexer\Model\ProcessManager; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; /** * Class Full reindex action * - * @package Magento\Catalog\Model\Indexer\Category\Product\Action * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Full extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction +class Full extends AbstractAction { /** - * @var \Magento\Framework\Indexer\BatchSizeManagementInterface + * @var BatchSizeManagementInterface */ private $batchSizeManagement; /** - * @var \Magento\Framework\Indexer\BatchProviderInterface + * @var BatchProviderInterface */ private $batchProvider; /** - * @var \Magento\Framework\EntityManager\MetadataPool + * @var MetadataPool */ protected $metadataPool; @@ -52,25 +65,25 @@ class Full extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio /** * @param ResourceConnection $resource - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Catalog\Model\Config $config + * @param StoreManagerInterface $storeManager + * @param Config $config * @param QueryGenerator|null $queryGenerator - * @param \Magento\Framework\Indexer\BatchSizeManagementInterface|null $batchSizeManagement - * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider - * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool + * @param BatchSizeManagementInterface|null $batchSizeManagement + * @param BatchProviderInterface|null $batchProvider + * @param MetadataPool|null $metadataPool * @param int|null $batchRowsCount * @param ActiveTableSwitcher|null $activeTableSwitcher * @param ProcessManager $processManager * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Framework\App\ResourceConnection $resource, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Catalog\Model\Config $config, + ResourceConnection $resource, + StoreManagerInterface $storeManager, + Config $config, QueryGenerator $queryGenerator = null, - \Magento\Framework\Indexer\BatchSizeManagementInterface $batchSizeManagement = null, - \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null, - \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, + BatchSizeManagementInterface $batchSizeManagement = null, + BatchProviderInterface $batchProvider = null, + MetadataPool $metadataPool = null, $batchRowsCount = null, ActiveTableSwitcher $activeTableSwitcher = null, ProcessManager $processManager = null @@ -81,15 +94,15 @@ public function __construct( $config, $queryGenerator ); - $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $objectManager = ObjectManager::getInstance(); $this->batchSizeManagement = $batchSizeManagement ?: $objectManager->get( - \Magento\Framework\Indexer\BatchSizeManagementInterface::class + BatchSizeManagementInterface::class ); $this->batchProvider = $batchProvider ?: $objectManager->get( - \Magento\Framework\Indexer\BatchProviderInterface::class + BatchProviderInterface::class ); $this->metadataPool = $metadataPool ?: $objectManager->get( - \Magento\Framework\EntityManager\MetadataPool::class + MetadataPool::class ); $this->batchRowsCount = $batchRowsCount; $this->activeTableSwitcher = $activeTableSwitcher ?: $objectManager->get(ActiveTableSwitcher::class); @@ -97,33 +110,39 @@ public function __construct( } /** + * Creates the store tables + * * @return void */ - private function createTables() + private function createTables(): void { foreach ($this->storeManager->getStores() as $store) { - $this->tableMaintainer->createTablesForStore($store->getId()); + $this->tableMaintainer->createTablesForStore((int)$store->getId()); } } /** + * Truncates the replica tables + * * @return void */ - private function clearReplicaTables() + private function clearReplicaTables(): void { foreach ($this->storeManager->getStores() as $store) { - $this->connection->truncateTable($this->tableMaintainer->getMainReplicaTable($store->getId())); + $this->connection->truncateTable($this->tableMaintainer->getMainReplicaTable((int)$store->getId())); } } /** + * Switches the active table + * * @return void */ - private function switchTables() + private function switchTables(): void { $tablesToSwitch = []; foreach ($this->storeManager->getStores() as $store) { - $tablesToSwitch[] = $this->tableMaintainer->getMainTable($store->getId()); + $tablesToSwitch[] = $this->tableMaintainer->getMainTable((int)$store->getId()); } $this->activeTableSwitcher->switchTable($this->connection, $tablesToSwitch); } @@ -133,12 +152,13 @@ private function switchTables() * * @return $this */ - public function execute() + public function execute(): self { $this->createTables(); $this->clearReplicaTables(); $this->reindex(); $this->switchTables(); + return $this; } @@ -147,7 +167,7 @@ public function execute() * * @return void */ - protected function reindex() + protected function reindex(): void { $userFunctions = []; @@ -165,9 +185,9 @@ protected function reindex() /** * Execute indexation by store * - * @param \Magento\Store\Model\Store $store + * @param Store $store */ - private function reindexStore($store) + private function reindexStore($store): void { $this->reindexRootCategory($store); $this->reindexAnchorCategories($store); @@ -177,31 +197,31 @@ private function reindexStore($store) /** * Publish data from tmp to replica table * - * @param \Magento\Store\Model\Store $store + * @param Store $store * @return void */ - private function publishData($store) + private function publishData($store): void { - $select = $this->connection->select()->from($this->tableMaintainer->getMainTmpTable($store->getId())); + $select = $this->connection->select()->from($this->tableMaintainer->getMainTmpTable((int)$store->getId())); $columns = array_keys( - $this->connection->describeTable($this->tableMaintainer->getMainReplicaTable($store->getId())) + $this->connection->describeTable($this->tableMaintainer->getMainReplicaTable((int)$store->getId())) ); - $tableName = $this->tableMaintainer->getMainReplicaTable($store->getId()); + $tableName = $this->tableMaintainer->getMainReplicaTable((int)$store->getId()); $this->connection->query( $this->connection->insertFromSelect( $select, $tableName, $columns, - \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + AdapterInterface::INSERT_ON_DUPLICATE ) ); } /** - * {@inheritdoc} + * @inheritdoc */ - protected function reindexRootCategory(\Magento\Store\Model\Store $store) + protected function reindexRootCategory(Store $store): void { if ($this->isIndexRootCategoryNeeded()) { $this->reindexCategoriesBySelect($this->getAllProducts($store), 'cp.entity_id IN (?)', $store); @@ -211,10 +231,10 @@ protected function reindexRootCategory(\Magento\Store\Model\Store $store) /** * Reindex products of anchor categories * - * @param \Magento\Store\Model\Store $store + * @param Store $store * @return void */ - protected function reindexAnchorCategories(\Magento\Store\Model\Store $store) + protected function reindexAnchorCategories(Store $store): void { $this->reindexCategoriesBySelect($this->getAnchorCategoriesSelect($store), 'ccp.product_id IN (?)', $store); } @@ -222,10 +242,10 @@ protected function reindexAnchorCategories(\Magento\Store\Model\Store $store) /** * Reindex products of non anchor categories * - * @param \Magento\Store\Model\Store $store + * @param Store $store * @return void */ - protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store) + protected function reindexNonAnchorCategories(Store $store): void { $this->reindexCategoriesBySelect($this->getNonAnchorCategoriesSelect($store), 'ccp.product_id IN (?)', $store); } @@ -233,40 +253,42 @@ protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store) /** * Reindex categories using given SQL select and condition. * - * @param \Magento\Framework\DB\Select $basicSelect + * @param Select $basicSelect * @param string $whereCondition - * @param \Magento\Store\Model\Store $store + * @param Store $store * @return void */ - private function reindexCategoriesBySelect(\Magento\Framework\DB\Select $basicSelect, $whereCondition, $store) + private function reindexCategoriesBySelect(Select $basicSelect, $whereCondition, $store): void { - $this->tableMaintainer->createMainTmpTable($store->getId()); + $this->tableMaintainer->createMainTmpTable((int)$store->getId()); - $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + $entityMetadata = $this->metadataPool->getMetadata(ProductInterface::class); $columns = array_keys( - $this->connection->describeTable($this->tableMaintainer->getMainTmpTable($store->getId())) + $this->connection->describeTable($this->tableMaintainer->getMainTmpTable((int)$store->getId())) ); $this->batchSizeManagement->ensureBatchSize($this->connection, $this->batchRowsCount); - $batches = $this->batchProvider->getBatches( - $this->connection, - $entityMetadata->getEntityTable(), + + $select = $this->connection->select(); + $select->distinct(true); + $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField()); + + $batchQueries = $this->prepareSelectsByRange( + $select, $entityMetadata->getIdentifierField(), - $this->batchRowsCount + (int)$this->batchRowsCount ); - foreach ($batches as $batch) { - $this->connection->delete($this->tableMaintainer->getMainTmpTable($store->getId())); + + foreach ($batchQueries as $query) { + $this->connection->delete($this->tableMaintainer->getMainTmpTable((int)$store->getId())); + $entityIds = $this->connection->fetchCol($query); $resultSelect = clone $basicSelect; - $select = $this->connection->select(); - $select->distinct(true); - $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField()); - $entityIds = $this->batchProvider->getBatchIds($this->connection, $select, $batch); $resultSelect->where($whereCondition, $entityIds); $this->connection->query( $this->connection->insertFromSelect( $resultSelect, - $this->tableMaintainer->getMainTmpTable($store->getId()), + $this->tableMaintainer->getMainTmpTable((int)$store->getId()), $columns, - \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + AdapterInterface::INSERT_ON_DUPLICATE ) ); $this->publishData($store); diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php index 802176092d147..ed8f692885d91 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php @@ -7,26 +7,41 @@ namespace Magento\Catalog\Model\Indexer\Product\Eav\Action; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction; use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Query\BatchIteratorInterface; +use Magento\Framework\DB\Query\Generator as QueryGenerator; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Indexer\BatchProviderInterface; +use Magento\Store\Model\ScopeInterface; /** * Class Full reindex action + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction +class Full extends AbstractAction { /** - * @var \Magento\Framework\EntityManager\MetadataPool + * @var MetadataPool */ private $metadataPool; /** - * @var \Magento\Framework\Indexer\BatchProviderInterface + * @var BatchProviderInterface */ private $batchProvider; /** - * @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator + * @var BatchSizeCalculator */ private $batchSizeCalculator; @@ -36,44 +51,54 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction private $activeTableSwitcher; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface + * @var ScopeConfigInterface */ private $scopeConfig; /** - * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory - * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory - * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool - * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider - * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator $batchSizeCalculator + * @var QueryGenerator|null + */ + private $batchQueryGenerator; + + /** + * @param DecimalFactory $eavDecimalFactory + * @param SourceFactory $eavSourceFactory + * @param MetadataPool|null $metadataPool + * @param BatchProviderInterface|null $batchProvider + * @param BatchSizeCalculator $batchSizeCalculator * @param ActiveTableSwitcher|null $activeTableSwitcher - * @param \Magento\Framework\App\Config\ScopeConfigInterface|null $scopeConfig + * @param ScopeConfigInterface|null $scopeConfig + * @param QueryGenerator|null $batchQueryGenerator */ public function __construct( - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory, - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory, - \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, - \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null, - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator $batchSizeCalculator = null, + DecimalFactory $eavDecimalFactory, + SourceFactory $eavSourceFactory, + MetadataPool $metadataPool = null, + BatchProviderInterface $batchProvider = null, + BatchSizeCalculator $batchSizeCalculator = null, ActiveTableSwitcher $activeTableSwitcher = null, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null + ScopeConfigInterface $scopeConfig = null, + QueryGenerator $batchQueryGenerator = null ) { - $this->scopeConfig = $scopeConfig ?: \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Framework\App\Config\ScopeConfigInterface::class + $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get( + ScopeConfigInterface::class ); parent::__construct($eavDecimalFactory, $eavSourceFactory, $scopeConfig); - $this->metadataPool = $metadataPool ?: \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Framework\EntityManager\MetadataPool::class + $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get( + MetadataPool::class ); - $this->batchProvider = $batchProvider ?: \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Framework\Indexer\BatchProviderInterface::class + $this->batchProvider = $batchProvider ?: ObjectManager::getInstance()->get( + BatchProviderInterface::class ); - $this->batchSizeCalculator = $batchSizeCalculator ?: \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator::class + $this->batchSizeCalculator = $batchSizeCalculator ?: ObjectManager::getInstance()->get( + BatchSizeCalculator::class ); - $this->activeTableSwitcher = $activeTableSwitcher ?: \Magento\Framework\App\ObjectManager::getInstance()->get( + $this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()->get( ActiveTableSwitcher::class ); + $this->batchQueryGenerator = $batchQueryGenerator ?: ObjectManager::getInstance()->get( + QueryGenerator::class + ); } /** @@ -81,10 +106,10 @@ public function __construct( * * @param array|int|null $ids * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function execute($ids = null) + public function execute($ids = null): void { if (!$this->isEavIndexerEnabled()) { return; @@ -94,20 +119,21 @@ public function execute($ids = null) $connection = $indexer->getConnection(); $mainTable = $this->activeTableSwitcher->getAdditionalTableName($indexer->getMainTable()); $connection->truncateTable($mainTable); - $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); - $batches = $this->batchProvider->getBatches( - $connection, - $entityMetadata->getEntityTable(), + $entityMetadata = $this->metadataPool->getMetadata(ProductInterface::class); + + $select = $connection->select(); + $select->distinct(true); + $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField()); + + $batchQueries = $this->batchQueryGenerator->generate( $entityMetadata->getIdentifierField(), - $this->batchSizeCalculator->estimateBatchSize($connection, $indexerName) + $select, + $this->batchSizeCalculator->estimateBatchSize($connection, $indexerName), + BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR ); - foreach ($batches as $batch) { - /** @var \Magento\Framework\DB\Select $select */ - $select = $connection->select(); - $select->distinct(true); - $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField()); - $entityIds = $this->batchProvider->getBatchIds($connection, $select, $batch); + foreach ($batchQueries as $query) { + $entityIds = $connection->fetchCol($query); if (!empty($entityIds)) { $indexer->reindexEntities($this->processRelations($indexer, $entityIds, true)); $this->syncData($indexer, $mainTable); @@ -116,14 +142,14 @@ public function execute($ids = null) $this->activeTableSwitcher->switchTable($indexer->getConnection(), [$indexer->getMainTable()]); } } catch (\Exception $e) { - throw new \Magento\Framework\Exception\LocalizedException(__($e->getMessage()), $e); + throw new LocalizedException(__($e->getMessage()), $e); } } /** * @inheritdoc */ - protected function syncData($indexer, $destinationTable, $ids = null) + protected function syncData($indexer, $destinationTable, $ids = null): void { $connection = $indexer->getConnection(); $connection->beginTransaction(); @@ -136,7 +162,7 @@ protected function syncData($indexer, $destinationTable, $ids = null) $select, $destinationTable, $targetColumns, - \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + AdapterInterface::INSERT_ON_DUPLICATE ); $connection->query($query); $connection->commit(); @@ -155,7 +181,7 @@ private function isEavIndexerEnabled(): bool { $eavIndexerStatus = $this->scopeConfig->getValue( self::ENABLE_EAV_INDEXER, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE ); return (bool)$eavIndexerStatus; diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php index 1a75751570658..9b83051e6334b 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php @@ -3,41 +3,66 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Catalog\Model\Indexer\Product\Price\Action; +use Exception; +use Magento\Catalog\Model\Indexer\Product\Price\AbstractAction; +use Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory; +use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory; +use Magento\Directory\Model\CurrencyFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Query\BatchIterator; +use Magento\Framework\DB\Query\Generator as QueryGenerator; +use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\EntityMetadataInterface; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Indexer\BatchProviderInterface; use Magento\Framework\Indexer\DimensionalIndexerInterface; use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Framework\Stdlib\DateTime; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Indexer\Model\ProcessManager; use Magento\Store\Model\Indexer\WebsiteDimensionProvider; +use Magento\Store\Model\StoreManagerInterface; +use SplFixedArray; /** * Class Full reindex action * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction +class Full extends AbstractAction { /** - * @var \Magento\Framework\EntityManager\MetadataPool + * @var MetadataPool */ private $metadataPool; /** - * @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator + * @var BatchSizeCalculator */ private $batchSizeCalculator; /** - * @var \Magento\Framework\Indexer\BatchProviderInterface + * @var BatchProviderInterface */ private $batchProvider; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher + * @var ActiveTableSwitcher */ private $activeTableSwitcher; @@ -47,54 +72,61 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction private $productMetaDataCached; /** - * @var \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory + * @var DimensionCollectionFactory */ private $dimensionCollectionFactory; /** - * @var \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer + * @var TableMaintainer */ private $dimensionTableMaintainer; /** - * @var \Magento\Indexer\Model\ProcessManager + * @var ProcessManager */ private $processManager; /** - * @param \Magento\Framework\App\Config\ScopeConfigInterface $config - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Directory\Model\CurrencyFactory $currencyFactory - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate - * @param \Magento\Framework\Stdlib\DateTime $dateTime - * @param \Magento\Catalog\Model\Product\Type $catalogProductType - * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory - * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource - * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool - * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator|null $batchSizeCalculator - * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider - * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|null $activeTableSwitcher - * @param \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory|null $dimensionCollectionFactory - * @param \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer|null $dimensionTableMaintainer - * @param \Magento\Indexer\Model\ProcessManager $processManager + * @var QueryGenerator|null + */ + private $batchQueryGenerator; + + /** + * @param ScopeConfigInterface $config + * @param StoreManagerInterface $storeManager + * @param CurrencyFactory $currencyFactory + * @param TimezoneInterface $localeDate + * @param DateTime $dateTime + * @param Type $catalogProductType + * @param Factory $indexerPriceFactory + * @param DefaultPrice $defaultIndexerResource + * @param MetadataPool|null $metadataPool + * @param BatchSizeCalculator|null $batchSizeCalculator + * @param BatchProviderInterface|null $batchProvider + * @param ActiveTableSwitcher|null $activeTableSwitcher + * @param DimensionCollectionFactory|null $dimensionCollectionFactory + * @param TableMaintainer|null $dimensionTableMaintainer + * @param ProcessManager $processManager + * @param QueryGenerator|null $batchQueryGenerator * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Framework\App\Config\ScopeConfigInterface $config, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Directory\Model\CurrencyFactory $currencyFactory, - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, - \Magento\Framework\Stdlib\DateTime $dateTime, - \Magento\Catalog\Model\Product\Type $catalogProductType, - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory, - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource, - \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator $batchSizeCalculator = null, - \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null, - \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null, - \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory $dimensionCollectionFactory = null, - \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer $dimensionTableMaintainer = null, - \Magento\Indexer\Model\ProcessManager $processManager = null + ScopeConfigInterface $config, + StoreManagerInterface $storeManager, + CurrencyFactory $currencyFactory, + TimezoneInterface $localeDate, + DateTime $dateTime, + Type $catalogProductType, + Factory $indexerPriceFactory, + DefaultPrice $defaultIndexerResource, + MetadataPool $metadataPool = null, + BatchSizeCalculator $batchSizeCalculator = null, + BatchProviderInterface $batchProvider = null, + ActiveTableSwitcher $activeTableSwitcher = null, + DimensionCollectionFactory $dimensionCollectionFactory = null, + TableMaintainer $dimensionTableMaintainer = null, + ProcessManager $processManager = null, + QueryGenerator $batchQueryGenerator = null ) { parent::__construct( $config, @@ -107,26 +139,27 @@ public function __construct( $defaultIndexerResource ); $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get( - \Magento\Framework\EntityManager\MetadataPool::class + MetadataPool::class ); $this->batchSizeCalculator = $batchSizeCalculator ?: ObjectManager::getInstance()->get( - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator::class + BatchSizeCalculator::class ); $this->batchProvider = $batchProvider ?: ObjectManager::getInstance()->get( - \Magento\Framework\Indexer\BatchProviderInterface::class + BatchProviderInterface::class ); $this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()->get( - \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class + ActiveTableSwitcher::class ); $this->dimensionCollectionFactory = $dimensionCollectionFactory ?: ObjectManager::getInstance()->get( - \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory::class + DimensionCollectionFactory::class ); $this->dimensionTableMaintainer = $dimensionTableMaintainer ?: ObjectManager::getInstance()->get( - \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer::class + TableMaintainer::class ); $this->processManager = $processManager ?: ObjectManager::getInstance()->get( - \Magento\Indexer\Model\ProcessManager::class + ProcessManager::class ); + $this->batchQueryGenerator = $batchQueryGenerator ?? ObjectManager::getInstance()->get(QueryGenerator::class); } /** @@ -134,16 +167,16 @@ public function __construct( * * @param array|int|null $ids * @return void - * @throws \Exception + * @throws Exception * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function execute($ids = null) + public function execute($ids = null): void { try { //Prepare indexer tables before full reindex $this->prepareTables(); - /** @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $indexer */ + /** @var DefaultPrice $indexer */ foreach ($this->getTypeIndexers(true) as $typeId => $priceIndexer) { if ($priceIndexer instanceof DimensionalIndexerInterface) { //New price reindex mechanism @@ -159,7 +192,7 @@ public function execute($ids = null) //Final replacement of tables from replica to main $this->switchTables(); - } catch (\Exception $e) { + } catch (Exception $e) { throw new LocalizedException(__($e->getMessage()), $e); } } @@ -168,9 +201,9 @@ public function execute($ids = null) * Prepare indexer tables before full reindex * * @return void - * @throws \Exception + * @throws Exception */ - private function prepareTables() + private function prepareTables(): void { $this->_defaultIndexerResource->getTableStrategy()->setUseIdxTable(false); @@ -183,9 +216,9 @@ private function prepareTables() * Truncate replica tables by dimensions * * @return void - * @throws \Exception + * @throws Exception */ - private function truncateReplicaTables() + private function truncateReplicaTables(): void { foreach ($this->dimensionCollectionFactory->create() as $dimension) { $dimensionTable = $this->dimensionTableMaintainer->getMainReplicaTable($dimension); @@ -200,14 +233,14 @@ private function truncateReplicaTables() * @param string $typeId * * @return void - * @throws \Exception + * @throws Exception */ - private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $priceIndexer, string $typeId) + private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $priceIndexer, string $typeId): void { $userFunctions = []; foreach ($this->dimensionCollectionFactory->create() as $dimensions) { $userFunctions[] = function () use ($priceIndexer, $dimensions, $typeId) { - return $this->reindexByBatches($priceIndexer, $dimensions, $typeId); + $this->reindexByBatches($priceIndexer, $dimensions, $typeId); }; } $this->processManager->execute($userFunctions); @@ -221,12 +254,15 @@ private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $p * @param string $typeId * * @return void - * @throws \Exception + * @throws Exception */ - private function reindexByBatches(DimensionalIndexerInterface $priceIndexer, array $dimensions, string $typeId) - { + private function reindexByBatches( + DimensionalIndexerInterface $priceIndexer, + array $dimensions, + string $typeId + ): void { foreach ($this->getBatchesForIndexer($typeId) as $batch) { - $this->reindexByBatchWithDimensions($priceIndexer, $batch, $dimensions, $typeId); + $this->reindexByBatchWithDimensions($priceIndexer, $batch, $dimensions); } } @@ -235,16 +271,20 @@ private function reindexByBatches(DimensionalIndexerInterface $priceIndexer, arr * * @param string $typeId * - * @return \Generator - * @throws \Exception + * @return BatchIterator + * @throws Exception */ - private function getBatchesForIndexer(string $typeId) + private function getBatchesForIndexer(string $typeId): BatchIterator { $connection = $this->_defaultIndexerResource->getConnection(); - return $this->batchProvider->getBatches( - $connection, - $this->getProductMetaData()->getEntityTable(), + $entityMetadata = $this->getProductMetaData(); + $select = $connection->select(); + $select->distinct(true); + $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField()); + + return $this->batchQueryGenerator->generate( $this->getProductMetaData()->getIdentifierField(), + $select, $this->batchSizeCalculator->estimateBatchSize( $connection, $typeId @@ -256,27 +296,25 @@ private function getBatchesForIndexer(string $typeId) * Reindex by batch for new 'Dimensional' price indexer * * @param DimensionalIndexerInterface $priceIndexer - * @param array $batch + * @param Select $batchQuery * @param array $dimensions - * @param string $typeId * * @return void - * @throws \Exception + * @throws Exception */ private function reindexByBatchWithDimensions( DimensionalIndexerInterface $priceIndexer, - array $batch, - array $dimensions, - string $typeId - ) { - $entityIds = $this->getEntityIdsFromBatch($typeId, $batch); + Select $batchQuery, + array $dimensions + ): void { + $entityIds = $this->getEntityIdsFromBatch($batchQuery); if (!empty($entityIds)) { $this->dimensionTableMaintainer->createMainTmpTable($dimensions); $temporaryTable = $this->dimensionTableMaintainer->getMainTmpTable($dimensions); $this->_emptyTable($temporaryTable); - $priceIndexer->executeByDimensions($dimensions, \SplFixedArray::fromArray($entityIds, false)); + $priceIndexer->executeByDimensions($dimensions, SplFixedArray::fromArray($entityIds, false)); // Sync data from temp table to index table $this->_insertFromTable( @@ -293,12 +331,12 @@ private function reindexByBatchWithDimensions( * @param string $typeId * * @return void - * @throws \Exception + * @throws Exception */ - private function reindexProductType(PriceInterface $priceIndexer, string $typeId) + private function reindexProductType(PriceInterface $priceIndexer, string $typeId): void { foreach ($this->getBatchesForIndexer($typeId) as $batch) { - $this->reindexBatch($priceIndexer, $batch, $typeId); + $this->reindexBatch($priceIndexer, $batch); } } @@ -306,15 +344,13 @@ private function reindexProductType(PriceInterface $priceIndexer, string $typeId * Reindex by batch for old price indexer * * @param PriceInterface $priceIndexer - * @param array $batch - * @param string $typeId - * + * @param Select $batch * @return void - * @throws \Exception + * @throws Exception */ - private function reindexBatch(PriceInterface $priceIndexer, array $batch, string $typeId) + private function reindexBatch(PriceInterface $priceIndexer, Select $batch): void { - $entityIds = $this->getEntityIdsFromBatch($typeId, $batch); + $entityIds = $this->getEntityIdsFromBatch($batch); if (!empty($entityIds)) { // Temporary table will created if not exists @@ -339,36 +375,24 @@ private function reindexBatch(PriceInterface $priceIndexer, array $batch, string /** * Get Entity Ids from batch * - * @param string $typeId - * @param array $batch - * + * @param Select $batch * @return array - * @throws \Exception + * @throws Exception */ - private function getEntityIdsFromBatch(string $typeId, array $batch) + private function getEntityIdsFromBatch(Select $batch): array { $connection = $this->_defaultIndexerResource->getConnection(); - // Get entity ids from batch - $select = $connection - ->select() - ->distinct(true) - ->from( - ['e' => $this->getProductMetaData()->getEntityTable()], - $this->getProductMetaData()->getIdentifierField() - ) - ->where('type_id = ?', $typeId); - - return $this->batchProvider->getBatchIds($connection, $select, $batch); + return $connection->fetchCol($batch); } /** * Get product meta data * * @return EntityMetadataInterface - * @throws \Exception + * @throws Exception */ - private function getProductMetaData() + private function getProductMetaData(): EntityMetadataInterface { if ($this->productMetaDataCached === null) { $this->productMetaDataCached = $this->metadataPool->getMetadata(ProductInterface::class); @@ -381,9 +405,9 @@ private function getProductMetaData() * Get replica table * * @return string - * @throws \Exception + * @throws Exception */ - private function getReplicaTable() + private function getReplicaTable(): string { return $this->activeTableSwitcher->getAdditionalTableName( $this->_defaultIndexerResource->getMainTable() @@ -395,7 +419,7 @@ private function getReplicaTable() * * @return void */ - private function switchTables() + private function switchTables(): void { // Switch dimension tables $mainTablesByDimension = []; @@ -417,13 +441,13 @@ private function switchTables() /** * Move data from old price indexer mechanism to new indexer mechanism by dimensions. + * * Used only for backward compatibility * * @param array $dimensions - * * @return void */ - private function moveDataFromReplicaTableToReplicaTables(array $dimensions) + private function moveDataFromReplicaTableToReplicaTables(array $dimensions): void { if (!$dimensions) { return; @@ -455,17 +479,17 @@ private function moveDataFromReplicaTableToReplicaTables(array $dimensions) $select, $replicaTablesByDimension, [], - \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + AdapterInterface::INSERT_ON_DUPLICATE ) ); } /** - * @deprecated + * Retrieves the index table that should be used * - * @inheritdoc + * @deprecated */ - protected function getIndexTargetTable() + protected function getIndexTargetTable(): string { return $this->activeTableSwitcher->getAdditionalTableName($this->_defaultIndexerResource->getMainTable()); } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php index 90c3f999a6a8b..2e1cff834fd34 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php @@ -3,15 +3,29 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Eav\Action; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Indexer\Product\Eav\Action\Full; use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Decimal; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Source; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Query\Generator; +use Magento\Framework\DB\Select; +use Magento\Framework\EntityManager\EntityMetadataInterface; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Indexer\BatchProviderInterface; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator; +use PHPUnit\Framework\MockObject\MockObject as MockObject; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -19,45 +33,50 @@ class FullTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Catalog\Model\Indexer\Product\Eav\Action\Full|\PHPUnit_Framework_MockObject_MockObject + * @var Full|MockObject */ private $model; /** - * @var DecimalFactory|\PHPUnit_Framework_MockObject_MockObject + * @var DecimalFactory|MockObject */ private $eavDecimalFactory; /** - * @var SourceFactory|\PHPUnit_Framework_MockObject_MockObject + * @var SourceFactory|MockObject */ private $eavSourceFactory; /** - * @var MetadataPool|\PHPUnit_Framework_MockObject_MockObject + * @var MetadataPool|MockObject */ private $metadataPool; /** - * @var BatchProviderInterface|\PHPUnit_Framework_MockObject_MockObject + * @var BatchProviderInterface|MockObject */ private $batchProvider; /** - * @var BatchSizeCalculator|\PHPUnit_Framework_MockObject_MockObject + * @var BatchSizeCalculator|MockObject */ private $batchSizeCalculator; /** - * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + * @var ActiveTableSwitcher|MockObject */ private $activeTableSwitcher; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ScopeConfigInterface|MockObject */ private $scopeConfig; + /** + * @var Generator + */ + private $batchQueryGenerator; + /** * @return void */ @@ -67,15 +86,16 @@ protected function setUp() $this->eavSourceFactory = $this->createPartialMock(SourceFactory::class, ['create']); $this->metadataPool = $this->createMock(MetadataPool::class); $this->batchProvider = $this->getMockForAbstractClass(BatchProviderInterface::class); + $this->batchQueryGenerator = $this->createMock(Generator::class); $this->batchSizeCalculator = $this->createMock(BatchSizeCalculator::class); $this->activeTableSwitcher = $this->createMock(ActiveTableSwitcher::class); - $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); $objectManager = new ObjectManager($this); $this->model = $objectManager->getObject( - \Magento\Catalog\Model\Indexer\Product\Eav\Action\Full::class, + Full::class, [ 'eavDecimalFactory' => $this->eavDecimalFactory, 'eavSourceFactory' => $this->eavSourceFactory, @@ -83,7 +103,8 @@ protected function setUp() 'batchProvider' => $this->batchProvider, 'batchSizeCalculator' => $this->batchSizeCalculator, 'activeTableSwitcher' => $this->activeTableSwitcher, - 'scopeConfig' => $this->scopeConfig + 'scopeConfig' => $this->scopeConfig, + 'batchQueryGenerator' => $this->batchQueryGenerator, ] ); } @@ -96,15 +117,15 @@ public function testExecute() $this->scopeConfig->expects($this->once())->method('getValue')->willReturn(1); $ids = [1, 2, 3]; - $connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + $connectionMock = $this->getMockBuilder(AdapterInterface::class) ->getMockForAbstractClass(); $connectionMock->expects($this->atLeastOnce())->method('describeTable')->willReturn(['id' => []]); - $eavSource = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Source::class) + $eavSource = $this->getMockBuilder(Source::class) ->disableOriginalConstructor() ->getMock(); - $eavDecimal = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Decimal::class) + $eavDecimal = $this->getMockBuilder(Decimal::class) ->disableOriginalConstructor() ->getMock(); @@ -125,22 +146,28 @@ public function testExecute() $this->eavSourceFactory->expects($this->once())->method('create')->will($this->returnValue($eavDecimal)); - $entityMetadataMock = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + $entityMetadataMock = $this->getMockBuilder(EntityMetadataInterface::class) ->getMockForAbstractClass(); $this->metadataPool->expects($this->atLeastOnce()) ->method('getMetadata') - ->with(\Magento\Catalog\Api\Data\ProductInterface::class) + ->with(ProductInterface::class) ->willReturn($entityMetadataMock); - $this->batchProvider->expects($this->atLeastOnce()) - ->method('getBatches') - ->willReturn([['from' => 10, 'to' => 100]]); - $this->batchProvider->expects($this->atLeastOnce()) - ->method('getBatchIds') + // Super inefficient algorithm in some cases + $this->batchProvider->expects($this->never()) + ->method('getBatches'); + + $batchQuery = $this->createMock(Select::class); + + $connectionMock->method('fetchCol') + ->with($batchQuery) ->willReturn($ids); - $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + $this->batchQueryGenerator->method('generate') + ->willReturn([$batchQuery]); + + $selectMock = $this->getMockBuilder(Select::class) ->disableOriginalConstructor() ->getMock(); @@ -153,7 +180,7 @@ public function testExecute() /** * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function testExecuteWithDisabledEavIndexer() { diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php index bc10d38173b4d..43a5aabee9779 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php @@ -6,12 +6,19 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogInventory\Model\Indexer\Stock\Action; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\CatalogInventory\Model\Indexer\Stock\BatchSizeManagement; +use Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\DefaultStock; use Magento\Framework\App\ResourceConnection; use Magento\CatalogInventory\Model\ResourceModel\Indexer\StockFactory; use Magento\Catalog\Model\Product\Type as ProductType; +use Magento\Framework\DB\Query\BatchIteratorInterface; +use Magento\Framework\DB\Query\Generator as QueryGenerator; use Magento\Framework\Indexer\CacheContext; use Magento\Framework\Event\ManagerInterface as EventManager; use Magento\Framework\EntityManager\MetadataPool; @@ -25,7 +32,6 @@ /** * Class Full reindex action * - * @package Magento\CatalogInventory\Model\Indexer\Stock\Action * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Full extends AbstractAction @@ -60,6 +66,11 @@ class Full extends AbstractAction */ private $activeTableSwitcher; + /** + * @var QueryGenerator|null + */ + private $batchQueryGenerator; + /** * @param ResourceConnection $resource * @param StockFactory $indexerFactory @@ -71,7 +82,7 @@ class Full extends AbstractAction * @param BatchProviderInterface|null $batchProvider * @param array $batchRowsCount * @param ActiveTableSwitcher|null $activeTableSwitcher - * + * @param QueryGenerator|null $batchQueryGenerator * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -84,7 +95,8 @@ public function __construct( BatchSizeManagementInterface $batchSizeManagement = null, BatchProviderInterface $batchProvider = null, array $batchRowsCount = [], - ActiveTableSwitcher $activeTableSwitcher = null + ActiveTableSwitcher $activeTableSwitcher = null, + QueryGenerator $batchQueryGenerator = null ) { parent::__construct( $resource, @@ -97,11 +109,12 @@ public function __construct( $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class); $this->batchProvider = $batchProvider ?: ObjectManager::getInstance()->get(BatchProviderInterface::class); $this->batchSizeManagement = $batchSizeManagement ?: ObjectManager::getInstance()->get( - \Magento\CatalogInventory\Model\Indexer\Stock\BatchSizeManagement::class + BatchSizeManagement::class ); $this->batchRowsCount = $batchRowsCount; $this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance() ->get(ActiveTableSwitcher::class); + $this->batchQueryGenerator = $batchQueryGenerator ?: ObjectManager::getInstance()->get(QueryGenerator::class); } /** @@ -109,22 +122,20 @@ public function __construct( * * @param null|array $ids * @throws LocalizedException - * * @return void - * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function execute($ids = null) + public function execute($ids = null): void { try { $this->useIdxTable(false); $this->cleanIndexersTables($this->_getTypeIndexers()); - $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + $entityMetadata = $this->metadataPool->getMetadata(ProductInterface::class); $columns = array_keys($this->_getConnection()->describeTable($this->_getIdxTable())); - /** @var \Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\DefaultStock $indexer */ + /** @var DefaultStock $indexer */ foreach ($this->_getTypeIndexers() as $indexer) { $indexer->setActionType(self::ACTION_TYPE); $connection = $indexer->getConnection(); @@ -135,22 +146,21 @@ public function execute($ids = null) : $this->batchRowsCount['default']; $this->batchSizeManagement->ensureBatchSize($connection, $batchRowCount); - $batches = $this->batchProvider->getBatches( - $connection, - $entityMetadata->getEntityTable(), + + $select = $connection->select(); + $select->distinct(true); + $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField()); + + $batchQueries = $this->batchQueryGenerator->generate( $entityMetadata->getIdentifierField(), - $batchRowCount + $select, + $batchRowCount, + BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR ); - foreach ($batches as $batch) { + foreach ($batchQueries as $query) { $this->clearTemporaryIndexTable(); - // Get entity ids from batch - $select = $connection->select(); - $select->distinct(true); - $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField()); - $select->where('type_id = ?', $indexer->getTypeId()); - - $entityIds = $this->batchProvider->getBatchIds($connection, $select, $batch); + $entityIds = $connection->fetchCol($query); if (!empty($entityIds)) { $indexer->reindexEntity($entityIds); $select = $connection->select()->from($this->_getIdxTable(), $columns); @@ -167,12 +177,13 @@ public function execute($ids = null) /** * Delete all records from index table + * * Used to clean table before re-indexation * * @param array $indexers * @return void */ - private function cleanIndexersTables(array $indexers) + private function cleanIndexersTables(array $indexers): void { $tables = array_map( function (StockInterface $indexer) { From 642a4ebee39ffef3ac4ae6e848bf5659fdcd1459 Mon Sep 17 00:00:00 2001 From: Jitheesh <jitheeshvo@gmail.com> Date: Fri, 22 Feb 2019 11:21:44 +0530 Subject: [PATCH 044/108] Checkout Page Cancel button is not working - Code style issues fixed --- .../Checkout/view/frontend/web/js/view/billing-address.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js index 4ea6fb5e5df75..d68b0682eb511 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js @@ -201,7 +201,7 @@ function ( this.isAddressDetailsVisible(true); } }, - + /** * Manage cancel button visibility */ From 5d2ffad7b23d1c7d6fe46d9288217ee5a1df4e5e Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Fri, 22 Feb 2019 09:59:32 +0200 Subject: [PATCH 045/108] Adjusting the Unit Tests --- .../Product/ProductCustomOptionsDataProviderTest.php | 11 ++++++++++- .../Ui/DataProvider/Product/ProductDataProvider.php | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php index 6d7c8814bd474..0e0cb676cdf3e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php @@ -54,7 +54,16 @@ protected function setUp() ->getMockForAbstractClass(); $this->collectionMock = $this->getMockBuilder(AbstractCollection::class) ->disableOriginalConstructor() - ->setMethods(['load', 'getSelect', 'getTable', 'getIterator', 'isLoaded', 'toArray', 'getSize']) + ->setMethods([ + 'load', + 'getSelect', + 'getTable', + 'getIterator', + 'isLoaded', + 'toArray', + 'getSize', + 'setStoreId' + ]) ->getMockForAbstractClass(); $this->dbSelectMock = $this->getMockBuilder(DbSelect::class) ->disableOriginalConstructor() diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php index edc2bcb66f7db..9a16356698263 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php @@ -68,7 +68,7 @@ public function __construct( $this->addFieldStrategies = $addFieldStrategies; $this->addFilterStrategies = $addFilterStrategies; $this->modifiersPool = $modifiersPool ?: ObjectManager::getInstance()->get(PoolInterface::class); - $this->getCollection()->setStoreId(Store::DEFAULT_STORE_ID); + $this->collection->setStoreId(Store::DEFAULT_STORE_ID); } /** From 4350a2e5a7ba4ddda00d50c6f55906a3d9f44e25 Mon Sep 17 00:00:00 2001 From: Nathan Smith <nathsmit@adobe.com> Date: Fri, 22 Feb 2019 07:52:16 -0600 Subject: [PATCH 046/108] MC-15004: Indexer hangs on category/products index - CR feedback --- .../Indexer/Category/Product/Action/Full.php | 2 +- .../Indexer/Product/Price/Action/Full.php | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) 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 e7b1e2c9bd62f..eb59acb56c356 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 @@ -110,7 +110,7 @@ public function __construct( } /** - * Creates the store tables + * Create the store tables * * @return void */ diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php index 9b83051e6334b..858eba3ab217a 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php @@ -8,7 +8,6 @@ namespace Magento\Catalog\Model\Indexer\Product\Price\Action; -use Exception; use Magento\Catalog\Model\Indexer\Product\Price\AbstractAction; use Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory; use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; @@ -37,7 +36,6 @@ use Magento\Indexer\Model\ProcessManager; use Magento\Store\Model\Indexer\WebsiteDimensionProvider; use Magento\Store\Model\StoreManagerInterface; -use SplFixedArray; /** * Class Full reindex action @@ -167,7 +165,7 @@ public function __construct( * * @param array|int|null $ids * @return void - * @throws Exception + * @throws \Exception * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute($ids = null): void @@ -192,7 +190,7 @@ public function execute($ids = null): void //Final replacement of tables from replica to main $this->switchTables(); - } catch (Exception $e) { + } catch (\Exception $e) { throw new LocalizedException(__($e->getMessage()), $e); } } @@ -201,7 +199,7 @@ public function execute($ids = null): void * Prepare indexer tables before full reindex * * @return void - * @throws Exception + * @throws \Exception */ private function prepareTables(): void { @@ -216,7 +214,7 @@ private function prepareTables(): void * Truncate replica tables by dimensions * * @return void - * @throws Exception + * @throws \Exception */ private function truncateReplicaTables(): void { @@ -233,7 +231,7 @@ private function truncateReplicaTables(): void * @param string $typeId * * @return void - * @throws Exception + * @throws \Exception */ private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $priceIndexer, string $typeId): void { @@ -254,7 +252,7 @@ private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $p * @param string $typeId * * @return void - * @throws Exception + * @throws \Exception */ private function reindexByBatches( DimensionalIndexerInterface $priceIndexer, @@ -272,7 +270,7 @@ private function reindexByBatches( * @param string $typeId * * @return BatchIterator - * @throws Exception + * @throws \Exception */ private function getBatchesForIndexer(string $typeId): BatchIterator { @@ -300,7 +298,7 @@ private function getBatchesForIndexer(string $typeId): BatchIterator * @param array $dimensions * * @return void - * @throws Exception + * @throws \Exception */ private function reindexByBatchWithDimensions( DimensionalIndexerInterface $priceIndexer, @@ -314,7 +312,7 @@ private function reindexByBatchWithDimensions( $temporaryTable = $this->dimensionTableMaintainer->getMainTmpTable($dimensions); $this->_emptyTable($temporaryTable); - $priceIndexer->executeByDimensions($dimensions, SplFixedArray::fromArray($entityIds, false)); + $priceIndexer->executeByDimensions($dimensions, \SplFixedArray::fromArray($entityIds, false)); // Sync data from temp table to index table $this->_insertFromTable( @@ -331,7 +329,7 @@ private function reindexByBatchWithDimensions( * @param string $typeId * * @return void - * @throws Exception + * @throws \Exception */ private function reindexProductType(PriceInterface $priceIndexer, string $typeId): void { @@ -346,7 +344,7 @@ private function reindexProductType(PriceInterface $priceIndexer, string $typeId * @param PriceInterface $priceIndexer * @param Select $batch * @return void - * @throws Exception + * @throws \Exception */ private function reindexBatch(PriceInterface $priceIndexer, Select $batch): void { @@ -377,7 +375,7 @@ private function reindexBatch(PriceInterface $priceIndexer, Select $batch): void * * @param Select $batch * @return array - * @throws Exception + * @throws \Exception */ private function getEntityIdsFromBatch(Select $batch): array { @@ -390,7 +388,7 @@ private function getEntityIdsFromBatch(Select $batch): array * Get product meta data * * @return EntityMetadataInterface - * @throws Exception + * @throws \Exception */ private function getProductMetaData(): EntityMetadataInterface { @@ -405,7 +403,7 @@ private function getProductMetaData(): EntityMetadataInterface * Get replica table * * @return string - * @throws Exception + * @throws \Exception */ private function getReplicaTable(): string { @@ -418,6 +416,7 @@ private function getReplicaTable(): string * Replacement of tables from replica to main * * @return void + * @throws \Zend_Db_Statement_Exception */ private function switchTables(): void { @@ -446,6 +445,7 @@ private function switchTables(): void * * @param array $dimensions * @return void + * @throws \Zend_Db_Statement_Exception */ private function moveDataFromReplicaTableToReplicaTables(array $dimensions): void { From a8c5504452b4c0e70746af4902f27dc68c40e02b Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza <enarc@atwix.com> Date: Sun, 24 Feb 2019 17:41:45 +0100 Subject: [PATCH 047/108] Improved tests coverage --- .../Quote/SetShippingMethodsOnCartTest.php | 25 ++++++++++++++++- .../_files/enable_all_shipping_methods.php | 21 ++++++++++++++ .../enable_all_shipping_methods_rollback.php | 17 +++++++++++ .../_files/tablerates_weight.php | 28 +++++++++++++++++++ .../_files/tablerates_weight_rollback.php | 7 +++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods.php create mode 100644 dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight.php create mode 100644 dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight_rollback.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php index 7aea8093e88a0..704672b29d0e3 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php @@ -55,6 +55,7 @@ protected function setUp() * Test for general routine of setting a shipping method on shopping cart * * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php */ public function testSetShippingMethodOnCart() { @@ -87,6 +88,7 @@ public function testSetShippingMethodOnCart() /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php */ public function testSetFlatrateOnCart() { @@ -100,19 +102,22 @@ public function testSetFlatrateOnCart() /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/OfflineShipping/_files/tablerates_weight.php + * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php */ public function testSetTableRatesOnCart() { $this->setShippingMethodAndCheckResponse( 'tablerate', 'bestway', - '15', + '10', 'Best Way - Table Rate' ); } /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php */ public function testSetFreeShippingOnCart() { @@ -126,6 +131,21 @@ public function testSetFreeShippingOnCart() /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php + */ + public function testSetUpsOnCart() + { + $this->setShippingMethodAndCheckResponse( + 'ups', + 'GND', + '15.61', + 'United Parcel Service - Ground' + ); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php */ public function testSetShippingMethodWithWrongCartId() { @@ -147,6 +167,7 @@ public function testSetShippingMethodWithWrongCartId() /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php */ public function testSetNonExistingShippingMethod() { @@ -174,6 +195,7 @@ public function testSetNonExistingShippingMethod() /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php */ public function testSetShippingMethodWithNonExistingAddress() { @@ -200,6 +222,7 @@ public function testSetShippingMethodWithNonExistingAddress() /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php */ public function testSetShippingMethodByGuestToCustomerCart() { diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods.php new file mode 100644 index 0000000000000..c0ee7f407caf2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods.php @@ -0,0 +1,21 @@ +<?php + +use Magento\Framework\App\Config\Storage\Writer; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Config\ScopeConfigInterface; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Writer $configWriter */ +$configWriter = $objectManager->create(WriterInterface::class); + +$configWriter->save('carriers/flatrate/active', 1); +$configWriter->save('carriers/tablerate/active', 1); +$configWriter->save('carriers/freeshipping/active', 1); +$configWriter->save('carriers/ups/active', 1); +$configWriter->save('carriers/usps/active', 1); +$configWriter->save('carriers/fedex/active', 1); +$configWriter->save('carriers/dhl/active', 1); + +$scopeConfig = $objectManager->get(ScopeConfigInterface::class); +$scopeConfig->clean(); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods_rollback.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods_rollback.php new file mode 100644 index 0000000000000..2071e84e53893 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods_rollback.php @@ -0,0 +1,17 @@ +<?php + +use Magento\Framework\App\Config\Storage\Writer; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Writer $configWriter */ +$configWriter = $objectManager->create(WriterInterface::class); + +$configWriter->delete('carriers/flatrate/active'); +$configWriter->delete('carriers/tablerate/active'); +$configWriter->delete('carriers/freeshipping/active'); +$configWriter->delete('carriers/ups/active'); +$configWriter->delete('carriers/usps/active'); +$configWriter->delete('carriers/fedex/active'); +$configWriter->delete('carriers/dhl/active'); diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight.php b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight.php new file mode 100644 index 0000000000000..99857304f6239 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class); +$connection = $resource->getConnection(); +$resourceModel = $objectManager->create(\Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate::class); +$entityTable = $resourceModel->getTable('shipping_tablerate'); +$data = + [ + 'website_id' => 1, + 'dest_country_id' => 'US', + 'dest_region_id' => 0, + 'dest_zip' => '*', + 'condition_name' => 'package_weight', + 'condition_value' => 0, + 'price' => 10, + 'cost' => 0 + ]; +$connection->query( + "INSERT INTO {$entityTable} (`website_id`, `dest_country_id`, `dest_region_id`, `dest_zip`, `condition_name`," + . "`condition_value`, `price`, `cost`) VALUES (:website_id, :dest_country_id, :dest_region_id, :dest_zip," + . " :condition_name, :condition_value, :price, :cost);", + $data +); diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight_rollback.php b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight_rollback.php new file mode 100644 index 0000000000000..6401407d35543 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight_rollback.php @@ -0,0 +1,7 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +require 'tablerates_rollback.php'; From 68ee3af4512ec5db3e8b65e66a59db5b061e5dd0 Mon Sep 17 00:00:00 2001 From: Shikha Mishra <shikhamishra@cedcoss.com> Date: Mon, 25 Feb 2019 22:38:49 +0530 Subject: [PATCH 048/108] Fixed #21425 Date design change show not correctly value in backend --- .../Magento/Backend/Block/System/Design/Edit/Tab/General.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Backend/Block/System/Design/Edit/Tab/General.php b/app/code/Magento/Backend/Block/System/Design/Edit/Tab/General.php index 5a09e1f17f617..3fe187e6e7694 100644 --- a/app/code/Magento/Backend/Block/System/Design/Edit/Tab/General.php +++ b/app/code/Magento/Backend/Block/System/Design/Edit/Tab/General.php @@ -90,7 +90,7 @@ protected function _prepareForm() ] ); - $dateFormat = $this->_localeDate->getDateFormat(\IntlDateFormatter::SHORT); + $dateFormat = $this->_localeDate->getDateFormatWithLongYear(); $fieldset->addField( 'date_from', 'date', From 11cfe04010a640f52be271cbb7d0ed129a9b900b Mon Sep 17 00:00:00 2001 From: Jitheesh <jitheeshvo@gmail.com> Date: Tue, 26 Feb 2019 14:30:25 +0530 Subject: [PATCH 049/108] Infinite redirects in Magento admin #21454 - fixed issue with start up page redirect --- app/code/Magento/Backend/App/Request/BackendValidator.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Backend/App/Request/BackendValidator.php b/app/code/Magento/Backend/App/Request/BackendValidator.php index 4d04d2fed8eb2..7ec6c83f29b50 100644 --- a/app/code/Magento/Backend/App/Request/BackendValidator.php +++ b/app/code/Magento/Backend/App/Request/BackendValidator.php @@ -146,8 +146,9 @@ private function createException( $exception = new InvalidRequestException($response); } else { //For regular requests. + $startPageUrl = $this->backendUrl->getStartupPageUrl(); $response = $this->redirectFactory->create() - ->setUrl($this->backendUrl->getStartupPageUrl()); + ->setUrl($this->backendUrl->getUrl($startPageUrl)); $exception = new InvalidRequestException( $response, [ From 3c8c4a7f9cba91e45ec890703a9a80e96a52a54c Mon Sep 17 00:00:00 2001 From: nmalevanec <mikola.malevanec@transoftgroup.com> Date: Tue, 26 Feb 2019 14:23:30 +0200 Subject: [PATCH 050/108] Fix static and functional tests. --- .../Magento/Tax/Plugin/Checkout/CustomerData/Cart.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php b/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php index c1b495d4ecf98..208833733ae3f 100644 --- a/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php +++ b/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php @@ -6,6 +6,10 @@ namespace Magento\Tax\Plugin\Checkout\CustomerData; +/** + * Process quote items price, considering tax configuration. + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class Cart { /** @@ -68,11 +72,13 @@ public function afterGetSectionData(\Magento\Checkout\CustomerData\Cart $subject $this->itemPriceRenderer->setItem($item); $this->itemPriceRenderer->setTemplate('checkout/cart/item/price/sidebar.phtml'); $result['items'][$key]['product_price']=$this->itemPriceRenderer->toHtml(); - if($this->itemPriceRenderer->displayPriceExclTax()) { + if ($this->itemPriceRenderer->displayPriceExclTax()) { $result['items'][$key]['product_price_value'] = $item->getCalculationPrice(); } elseif ($this->itemPriceRenderer->displayPriceInclTax()) { $result['items'][$key]['product_price_value'] = $item->getPriceInclTax(); } elseif ($this->itemPriceRenderer->displayBothPrices()) { + //unset product price value in case price already has been set as scalar value. + unset($result['items'][$key]['product_price_value']); $result['items'][$key]['product_price_value']['incl_tax'] = $item->getPriceInclTax(); $result['items'][$key]['product_price_value']['excl_tax'] = $item->getCalculationPrice(); } From 317b3aa19fb49d6941ab97a45f3b6afb01d15a1f Mon Sep 17 00:00:00 2001 From: serhii balko <serhii.balko@transoftgroup.com> Date: Tue, 26 Feb 2019 14:34:49 +0200 Subject: [PATCH 051/108] MAGETWO-98366: [Magento Cloud] Recalculation of Tier prices after customer logs in --- .../Model/Product/Type/Configurable/Price.php | 9 +- .../Product/Type/Configurable/PriceTest.php | 109 +++++++++++++++--- 2 files changed, 102 insertions(+), 16 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php index bee334596e990..3b25a5e594504 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php @@ -7,13 +7,15 @@ */ namespace Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Catalog\Model\Product; + class Price extends \Magento\Catalog\Model\Product\Type\Price { /** * Get product final price * * @param float $qty - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @return float */ public function getFinalPrice($qty, $product) @@ -22,7 +24,10 @@ public function getFinalPrice($qty, $product) return $product->getCalculatedFinalPrice(); } if ($product->getCustomOption('simple_product') && $product->getCustomOption('simple_product')->getProduct()) { - $finalPrice = parent::getFinalPrice($qty, $product->getCustomOption('simple_product')->getProduct()); + /** @var Product $simpleProduct */ + $simpleProduct = $product->getCustomOption('simple_product')->getProduct(); + $simpleProduct->setCustomerGroupId($product->getCustomerGroupId()); + $finalPrice = parent::getFinalPrice($qty, $simpleProduct); } else { $priceInfo = $product->getPriceInfo(); $finalPrice = $priceInfo->getPrice('final_price')->getAmount()->getValue(); diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php index 64b9b3776442a..ab52d4eb86021 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php @@ -6,22 +6,47 @@ namespace Magento\ConfigurableProduct\Test\Unit\Model\Product\Type\Configurable; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Configuration\Item\Option; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price as ConfigurablePrice; +use Magento\Framework\Event\ManagerInterface; +use Magento\Framework\Pricing\Amount\AmountInterface; +use Magento\Framework\Pricing\Price\PriceInterface; +use Magento\Framework\Pricing\PriceInfo\Base as PriceInfoBase; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; class PriceTest extends \PHPUnit\Framework\TestCase { - /** @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price */ + /** + * @var ObjectManagerHelper + */ + protected $objectManagerHelper; + + /** + * @var ConfigurablePrice + */ protected $model; - /** @var ObjectManagerHelper */ - protected $objectManagerHelper; + /** + * @var ManagerInterface|MockObject + */ + private $eventManagerMock; + /** + * @inheritdoc + */ protected function setUp() { $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->eventManagerMock = $this->createPartialMock( + ManagerInterface::class, + ['dispatch'] + ); $this->model = $this->objectManagerHelper->getObject( - \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price::class + ConfigurablePrice::class, + ['eventManager' => $this->eventManagerMock] ); } @@ -29,29 +54,29 @@ public function testGetFinalPrice() { $finalPrice = 10; $qty = 1; - $configurableProduct = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->setMethods(['getCustomOption', 'getPriceInfo', 'setFinalPrice', '__wakeUp']) - ->getMock(); - $customOption = $this->getMockBuilder(\Magento\Catalog\Model\Product\Configuration\Item\Option::class) + + /** @var Product|MockObject $configurableProduct */ + $configurableProduct = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() - ->setMethods(['getProduct']) + ->setMethods(['getCustomOption', 'getPriceInfo', 'setFinalPrice']) ->getMock(); - $priceInfo = $this->getMockBuilder(\Magento\Framework\Pricing\PriceInfo\Base::class) + /** @var PriceInfoBase|MockObject $priceInfo */ + $priceInfo = $this->getMockBuilder(PriceInfoBase::class) ->disableOriginalConstructor() ->setMethods(['getPrice']) ->getMock(); - $price = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class) + /** @var PriceInterface|MockObject $price */ + $price = $this->getMockBuilder(PriceInterface::class) ->disableOriginalConstructor() ->getMock(); - $amount = $this->getMockBuilder(\Magento\Framework\Pricing\Amount\AmountInterface::class) + /** @var AmountInterface|MockObject $amount */ + $amount = $this->getMockBuilder(AmountInterface::class) ->disableOriginalConstructor() ->getMock(); $configurableProduct->expects($this->any()) ->method('getCustomOption') ->willReturnMap([['simple_product', false], ['option_ids', false]]); - $customOption->expects($this->never())->method('getProduct'); $configurableProduct->expects($this->once())->method('getPriceInfo')->willReturn($priceInfo); $priceInfo->expects($this->once())->method('getPrice')->with('final_price')->willReturn($price); $price->expects($this->once())->method('getAmount')->willReturn($amount); @@ -60,4 +85,60 @@ public function testGetFinalPrice() $this->assertEquals($finalPrice, $this->model->getFinalPrice($qty, $configurableProduct)); } + + public function testGetFinalPriceWithSimpleProduct() + { + $finalPrice = 10; + $qty = 1; + $customerGroupId = 1; + + /** @var Product|MockObject $configurableProduct */ + $configurableProduct = $this->createPartialMock( + Product::class, + ['getCustomOption', 'setFinalPrice', 'getCustomerGroupId'] + ); + /** @var Option|MockObject $customOption */ + $customOption = $this->createPartialMock( + Option::class, + ['getProduct'] + ); + /** @var Product|MockObject $simpleProduct */ + $simpleProduct = $this->createPartialMock( + Product::class, + ['setCustomerGroupId', 'setFinalPrice', 'getPrice', 'getTierPrice', 'getData', 'getCustomOption'] + ); + + $configurableProduct->method('getCustomOption') + ->willReturnMap([ + ['simple_product', $customOption], + ['option_ids', false] + ]); + $configurableProduct->method('getCustomerGroupId')->willReturn($customerGroupId); + $configurableProduct->expects($this->atLeastOnce()) + ->method('setFinalPrice') + ->with($finalPrice) + ->willReturnSelf(); + $customOption->method('getProduct')->willReturn($simpleProduct); + $simpleProduct->expects($this->atLeastOnce()) + ->method('setCustomerGroupId') + ->with($customerGroupId) + ->willReturnSelf(); + $simpleProduct->method('getPrice')->willReturn($finalPrice); + $simpleProduct->method('getTierPrice')->with($qty)->willReturn($finalPrice); + $simpleProduct->expects($this->atLeastOnce()) + ->method('setFinalPrice') + ->with($finalPrice) + ->willReturnSelf(); + $simpleProduct->method('getData')->with('final_price')->willReturn($finalPrice); + $simpleProduct->method('getCustomOption')->with('option_ids')->willReturn(false); + $this->eventManagerMock->expects($this->once()) + ->method('dispatch') + ->with('catalog_product_get_final_price', ['product' => $simpleProduct, 'qty' => $qty]); + + $this->assertEquals( + $finalPrice, + $this->model->getFinalPrice($qty, $configurableProduct), + 'The final price calculation is wrong' + ); + } } From 1ad16118b87a983520ec10250aba1d309f2e971f Mon Sep 17 00:00:00 2001 From: serhii balko <serhii.balko@transoftgroup.com> Date: Tue, 26 Feb 2019 15:49:51 +0200 Subject: [PATCH 052/108] MAGETWO-98366: [Magento Cloud] Recalculation of Tier prices after customer logs in --- .../Model/Product/Type/Configurable/Price.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php index 3b25a5e594504..f2bf3116af9e4 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php @@ -9,14 +9,13 @@ use Magento\Catalog\Model\Product; +/** + * Class Price for configurable product + */ class Price extends \Magento\Catalog\Model\Product\Type\Price { /** - * Get product final price - * - * @param float $qty - * @param Product $product - * @return float + * @inheritdoc */ public function getFinalPrice($qty, $product) { @@ -40,7 +39,7 @@ public function getFinalPrice($qty, $product) } /** - * {@inheritdoc} + * @inheritdoc */ public function getPrice($product) { @@ -53,6 +52,7 @@ public function getPrice($product) } } } + return 0; } } From 14dbb4f3470780daa74644dc1450ec6e99cdcaf2 Mon Sep 17 00:00:00 2001 From: nmalevanec <mikola.malevanec@transoftgroup.com> Date: Tue, 26 Feb 2019 17:22:46 +0200 Subject: [PATCH 053/108] Fix static test. --- .../Catalog/Ui/DataProvider/Product/ProductDataProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php index 9a16356698263..a518afc576d61 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php @@ -112,7 +112,7 @@ public function addField($field, $alias = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function addFilter(\Magento\Framework\Api\Filter $filter) { From f14ffca4a66a67a6af31e6491b1e2debb3419986 Mon Sep 17 00:00:00 2001 From: Nathan Smith <nathsmit@adobe.com> Date: Tue, 26 Feb 2019 10:52:38 -0600 Subject: [PATCH 054/108] MAGETWO-98357: The required product attribute is not displayed when change attribute set --- .../Controller/Adminhtml/Product/Builder.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php index 125406061aed7..5c87ee7c9fcac 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Controller\Adminhtml\Product; use Magento\Catalog\Model\ProductFactory; @@ -15,6 +17,11 @@ use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Type as ProductTypes; +/** + * Build a product based on a request + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Builder { /** @@ -81,8 +88,9 @@ public function __construct( * @param RequestInterface $request * @return \Magento\Catalog\Model\Product * @throws \RuntimeException + * @throws \Magento\Framework\Exception\LocalizedException */ - public function build(RequestInterface $request) + public function build(RequestInterface $request): Product { $productId = (int) $request->getParam('id'); $storeId = $request->getParam('store', 0); @@ -92,6 +100,7 @@ public function build(RequestInterface $request) if ($productId) { try { $product = $this->productRepository->getById($productId, true, $storeId); + $product->setAttributeSetId($attributeSetId); } catch (\Exception $e) { $product = $this->createEmptyProduct(ProductTypes::DEFAULT_TYPE, $attributeSetId, $storeId); $this->logger->critical($e); @@ -113,6 +122,8 @@ public function build(RequestInterface $request) } /** + * Create a product with the given properties + * * @param int $typeId * @param int $attributeSetId * @param int $storeId From bcf7d9c0ef8cbd514774c24017e3d83a60353538 Mon Sep 17 00:00:00 2001 From: Nathan Smith <nathsmit@adobe.com> Date: Tue, 26 Feb 2019 13:11:04 -0600 Subject: [PATCH 055/108] MAGETWO-98357: The required product attribute is not displayed when change attribute set --- .../Catalog/Controller/Adminhtml/Product/Builder.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php index 5c87ee7c9fcac..78ad9f423871f 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php @@ -7,6 +7,7 @@ namespace Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ProductFactory; use Magento\Cms\Model\Wysiwyg as WysiwygModel; use Magento\Framework\App\RequestInterface; @@ -86,11 +87,11 @@ public function __construct( * Build product based on user request * * @param RequestInterface $request - * @return \Magento\Catalog\Model\Product + * @return ProductInterface * @throws \RuntimeException * @throws \Magento\Framework\Exception\LocalizedException */ - public function build(RequestInterface $request): Product + public function build(RequestInterface $request): ProductInterface { $productId = (int) $request->getParam('id'); $storeId = $request->getParam('store', 0); @@ -100,7 +101,9 @@ public function build(RequestInterface $request): Product if ($productId) { try { $product = $this->productRepository->getById($productId, true, $storeId); - $product->setAttributeSetId($attributeSetId); + if ($attributeSetId) { + $product->setAttributeSetId($attributeSetId); + } } catch (\Exception $e) { $product = $this->createEmptyProduct(ProductTypes::DEFAULT_TYPE, $attributeSetId, $storeId); $this->logger->critical($e); From e6d7b13a678014faebac0fac0de805d43618a9d5 Mon Sep 17 00:00:00 2001 From: Nathan Smith <nathsmit@adobe.com> Date: Tue, 26 Feb 2019 15:56:29 -0600 Subject: [PATCH 056/108] MAGETWO-98357: The required product attribute is not displayed when change attribute set --- .../Test/AdminChangeProductAttributeSet.xml | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml new file mode 100644 index 0000000000000..73280249eb68d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminChangeProductAttributeSet"> + <annotations> + <features value="Checkout"/> + <stories value="The required product attribute is not displayed when change attribute set"/> + <title value="Attributes from the selected attribute set should be shown"/> + <description value="Attributes from the selected attribute set should be shown"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-98452"/> + <useCaseId value="MAGETWO-98357"/> + <group value="catalog"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <createData entity="productAttributeWithTwoOptions" stepKey="createProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createProductAttributeOption1"> + <requiredEntity createDataKey="createProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createProductAttributeOption2"> + <requiredEntity createDataKey="createProductAttribute"/> + </createData> + <createData entity="CatalogAttributeSet" stepKey="createAttributeSet"/> + + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <amOnPage url="{{AdminProductAttributeSetEditPage.url}}/$$createAttributeSet.attribute_set_id$$}}/" stepKey="onAttributeSetEdit"/> + <actionGroup ref="AssignAttributeToGroup" stepKey="assignAttributeToGroup"> + <argument name="group" value="Product Details"/> + <argument name="attribute" value="$$createProductAttribute.attribute_code$$"/> + </actionGroup> + <actionGroup ref="SaveAttributeSet" stepKey="SaveAttributeSet"/> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProductAttribute" stepKey="deleteProductAttribute"/> + <deleteData createDataKey="createAttributeSet" stepKey="deleteAttributeSet"/> + </after> + + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct1"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + + <click selector="{{AdminProductFormSection.attributeSet}}" stepKey="startEditAttrSet"/> + <fillField selector="{{AdminProductFormSection.attributeSetFilter}}" userInput="$$createAttributeSet.attribute_set_name$$" stepKey="searchForAttrSet"/> + <click selector="{{AdminProductFormSection.attributeSetFilterResult}}" stepKey="selectAttrSet"/> + + <waitForText userInput="$$createProductAttribute.default_frontend_label$$" stepKey="seeAttributeInForm"/> + </test> +</tests> From 9dc9829da45de1d2221c2924a507c5e64629209c Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Wed, 27 Feb 2019 16:07:39 +0200 Subject: [PATCH 057/108] Covering the guest order view by integration tests --- .../Sales/Controller/Guest/FormTest.php | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php new file mode 100644 index 0000000000000..91e1415035e2b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Controller; + +use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Message\MessageInterface; +use Magento\Customer\Model\Session; +use Magento\TestFramework\Request; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * @magentoAppIsolation enabled + */ +class FormTest extends AbstractController +{ + /** + * Test view order as guest with correct data + * + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testViewOrderAsGuest() + { + $this->prepareRequestData(); + $this->dispatch('sales/guest/view/'); + $content = $this->getResponse()->getBody(); + $this->assertContains('Order # 100000001', $content); + } + + /** + * View order as logged in customer + * + * @magentoDataFixture Magento/Sales/_files/order.php + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testViewOrderAsLoggedIn() + { + $this->login(1); + $this->dispatch('sales/guest/view/'); + $this->assertRedirect($this->stringContains('sales/order/history/')); + } + + /** + * Test Return Order for guest with incorrect data + */ + public function testViewOrderAsGuestWithIncorrectData() + { + $this->prepareRequestData(true); + $this->dispatch('sales/guest/view/'); + $this->assertSessionMessages( + $this->equalTo(['You entered incorrect data. Please try again.']), + MessageInterface::TYPE_ERROR + ); + } + + /** + * Login the user + * + * @param string $customerId Customer to mark as logged in for the session + * @return void + */ + protected function login($customerId) + { + /** @var Session $session */ + $session = $this->_objectManager->get(Session::class); + $session->loginById($customerId); + } + + /** + * @param bool $invalidData + * @return void + */ + private function prepareRequestData($invalidData = false) + { + $orderId = 100000001; + $email = $invalidData ? 'wrong@example.com' : 'customer@null.com'; + + /** @var FormKey $formKey */ + $formKey = $this->_objectManager->get(FormKey::class); + $post = [ + 'oar_order_id' => $orderId, + 'oar_billing_lastname' => 'lastname', + 'oar_type' => 'email', + 'oar_email' => $email, + 'oar_zip' => '', + 'form_key' => $formKey->getFormKey(), + ]; + + $this->getRequest()->setMethod(Request::METHOD_POST); + $this->getRequest()->setPostValue($post); + } +} From 37c10bc108c4b148d7e98cb1d79b504cf0091ff4 Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Wed, 27 Feb 2019 16:14:11 +0200 Subject: [PATCH 058/108] Covering the guest order view by integration tests --- .../testsuite/Magento/Sales/Controller/Guest/FormTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php index 91e1415035e2b..d1ea911405fb3 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php @@ -7,9 +7,9 @@ namespace Magento\Sales\Controller; +use Magento\Customer\Model\Session; use Magento\Framework\Data\Form\FormKey; use Magento\Framework\Message\MessageInterface; -use Magento\Customer\Model\Session; use Magento\TestFramework\Request; use Magento\TestFramework\TestCase\AbstractController; From a146f50fbf97d637e977105d384d9c09db39ce53 Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Wed, 27 Feb 2019 16:22:12 +0200 Subject: [PATCH 059/108] Covering the logged in to access the Returns form use case --- .../Magento/Sales/Controller/Guest/FormTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php index d1ea911405fb3..4ca8b361f0473 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php @@ -44,6 +44,19 @@ public function testViewOrderAsLoggedIn() $this->assertRedirect($this->stringContains('sales/order/history/')); } + /** + * Test attempting to open the Returns form as logged in customer + * + * @magentoDataFixture Magento/Sales/_files/order.php + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testAttemptToOpenTheFormAsLoggedIn() + { + $this->login(1); + $this->dispatch('sales/guest/form/'); + $this->assertRedirect($this->stringContains('customer/account')); + } + /** * Test Return Order for guest with incorrect data */ From 026e68f0ed1b5c7eb126eddc8c53004e9bcfea89 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza <enarc@atwix.com> Date: Wed, 27 Feb 2019 16:25:22 +0100 Subject: [PATCH 060/108] Extended product customizable options coverage --- .../Magento/CatalogGraphQl/etc/graphql/di.xml | 4 ++ .../CatalogGraphQl/etc/schema.graphqls | 26 +++++++++++ .../Model/Cart/AddSimpleProductToCart.php | 45 ++++++++++++++++--- .../CustomizableOptionValue/Text.php | 1 + 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml index 7e18ac34f0fcc..b5622e948b7cd 100644 --- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml @@ -38,11 +38,15 @@ </item> <item name="customizable_options" xsi:type="array"> <item name="field" xsi:type="string">CustomizableFieldOption</item> + <item name="date" xsi:type="string">CustomizableDateOption</item> <item name="date_time" xsi:type="string">CustomizableDateOption</item> + <item name="time" xsi:type="string">CustomizableDateOption</item> <item name="file" xsi:type="string">CustomizableFileOption</item> <item name="area" xsi:type="string">CustomizableAreaOption</item> <item name="drop_down" xsi:type="string">CustomizableDropDownOption</item> + <item name="multiple" xsi:type="string">CustomizableMultipleOption</item> <item name="radio" xsi:type="string">CustomizableRadioOption</item> + <item name="checkbox" xsi:type="string">CustomizableCheckboxOption</item> </item> </argument> </arguments> diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 45f3a4c83be7b..d4b724b342acf 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -323,6 +323,19 @@ type CustomizableDropDownValue @doc(description: "CustomizableDropDownValue defi sort_order: Int @doc(description: "The order in which the option is displayed") } +type CustomizableMultipleOption implements CustomizableOptionInterface @doc(description: "CustomizableMultipoleOption contains information about a multiselect that is defined as part of a customizable option") { + value: [CustomizableMultipleValue] @doc(description: "An array that defines the set of options for a multiselect") +} + +type CustomizableMultipleValue @doc(description: "CustomizableMultipleValue defines the price and sku of a product whose page contains a customized multiselect") { + option_type_id: Int @doc(description: "The ID assigned to the value") + price: Float @doc(description: "The price assigned to this option") + price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC") + sku: String @doc(description: "The Stock Keeping Unit for this option") + title: String @doc(description: "The display name for this option") + sort_order: Int @doc(description: "The order in which the option is displayed") +} + type CustomizableFieldOption implements CustomizableOptionInterface @doc(description: "CustomizableFieldOption contains information about a text field that is defined as part of a customizable option") { value: CustomizableFieldValue @doc(description: "An object that defines a text field") product_sku: String @doc(description: "The Stock Keeping Unit of the base product") @@ -407,6 +420,19 @@ type CustomizableRadioValue @doc(description: "CustomizableRadioValue defines t sort_order: Int @doc(description: "The order in which the radio button is displayed") } +type CustomizableCheckboxOption implements CustomizableOptionInterface @doc(description: "CustomizableCheckbbixOption contains information about a set of checkbox values that are defined as part of a customizable option") { + value: [CustomizableCheckboxValue] @doc(description: "An array that defines a set of checkbox values") +} + +type CustomizableCheckboxValue @doc(description: "CustomizableCheckboxValue defines the price and sku of a product whose page contains a customized set of checkbox values") { + option_type_id: Int @doc(description: "The ID assigned to the value") + price: Float @doc(description: "The price assigned to this option") + price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC") + sku: String @doc(description: "The Stock Keeping Unit for this option") + title: String @doc(description: "The display name for this option") + sort_order: Int @doc(description: "The order in which the checkbox value is displayed") +} + type VirtualProduct implements ProductInterface, CustomizableProductInterface @doc(description: "A virtual product is non-tangible product that does not require shipping and is not kept in inventory") { } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php index 1b32866ed883c..b9f2bc88a9cd1 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php @@ -7,6 +7,8 @@ namespace Magento\QuoteGraphQl\Model\Cart; +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\DataObject; use Magento\Framework\DataObjectFactory; @@ -23,6 +25,11 @@ */ class AddSimpleProductToCart { + /** + * @var ProductCustomOptionRepositoryInterface + */ + private $customOptionRepository; + /** * @var ArrayManager */ @@ -42,15 +49,18 @@ class AddSimpleProductToCart * @param ArrayManager $arrayManager * @param DataObjectFactory $dataObjectFactory * @param ProductRepositoryInterface $productRepository + * @param ProductCustomOptionRepositoryInterface $customOptionRepository */ public function __construct( ArrayManager $arrayManager, DataObjectFactory $dataObjectFactory, - ProductRepositoryInterface $productRepository + ProductRepositoryInterface $productRepository, + ProductCustomOptionRepositoryInterface $customOptionRepository ) { $this->arrayManager = $arrayManager; $this->dataObjectFactory = $dataObjectFactory; $this->productRepository = $productRepository; + $this->customOptionRepository = $customOptionRepository; } /** @@ -67,7 +77,7 @@ public function execute(Quote $cart, array $cartItemData): void { $sku = $this->extractSku($cartItemData); $qty = $this->extractQty($cartItemData); - $customizableOptions = $this->extractCustomizableOptions($cartItemData); + $customizableOptions = $this->extractCustomizableOptions($cartItemData, $sku); try { $product = $this->productRepository->get($sku); @@ -127,15 +137,23 @@ private function extractQty(array $cartItemData): float * Extract Customizable Options from cart item data * * @param array $cartItemData + * @param string $sku * @return array */ - private function extractCustomizableOptions(array $cartItemData): array + private function extractCustomizableOptions(array $cartItemData, string $sku): array { $customizableOptions = $this->arrayManager->get('customizable_options', $cartItemData, []); - + $productCustomOptions = $this->customOptionRepository->getList($sku); + $productCustomOptionsMap = $this->getProductCustomOptionsMap($productCustomOptions); $customizableOptionsData = []; + foreach ($customizableOptions as $customizableOption) { - $customizableOptionsData[$customizableOption['id']] = $customizableOption['value']; + $multipleOptionTypesList = ['multiple', 'checkbox']; // TODO: use constants + if (in_array($productCustomOptionsMap[$customizableOption['id']]->getType(), $multipleOptionTypesList)) { + $customizableOptionsData[$customizableOption['id']] = explode(',', $customizableOption['value']); + } else { + $customizableOptionsData[$customizableOption['id']] = $customizableOption['value']; + } } return $customizableOptionsData; } @@ -156,4 +174,21 @@ private function createBuyRequest(float $qty, array $customOptions): DataObject ], ]); } + + /** + * Creates an array with a key equals option ID + * + * @param array $productCustomOptions + * @return array + */ + private function getProductCustomOptionsMap(array $productCustomOptions): array + { + $customOptionsData = []; + /** @var ProductCustomOptionInterface $productCustomOption */ + foreach ($productCustomOptions as $productCustomOption) { + $customOptionsData[$productCustomOption->getOptionId()] = $productCustomOption; + } + + return $customOptionsData; + } } diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Text.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Text.php index 4b29eb6a4a663..96f11badac82e 100644 --- a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Text.php +++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Text.php @@ -42,6 +42,7 @@ public function getData( ): array { /** @var TextOptionType $optionTypeRenderer */ $optionTypeRenderer = $option->groupFactory($option->getType()); + $optionTypeRenderer->setOption($option); $priceValueUnits = $this->priceUnitLabel->getData($option->getPriceType()); $selectedOptionValueData = [ From 4db921f0dbc31f0a1d1bd6dc8937593c9951ecb6 Mon Sep 17 00:00:00 2001 From: Valerii Naida <vnayda@adobe.com> Date: Wed, 27 Feb 2019 11:40:54 -0600 Subject: [PATCH 061/108] GraphQL-293: When maxSaleQty is set and qty is more than maxSaleQty the "The most you may purchase is %1." message should be sent instead of "Internal server error" --- .../Model/Cart/AddProductsToCart.php | 7 + .../CatalogInventory/AddProductToCartTest.php | 123 ++++++++++++++++++ .../Quote/AddSimpleProductToCartTest.php | 68 ++-------- 3 files changed, 143 insertions(+), 55 deletions(-) create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php index 6deb387476d5f..005cf3a10ca80 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php @@ -46,6 +46,7 @@ public function __construct( * @param array $cartItems * @throws GraphQlInputException * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException */ public function execute(Quote $cart, array $cartItems): void { @@ -53,6 +54,12 @@ public function execute(Quote $cart, array $cartItems): void $this->addProductToCart->execute($cart, $cartItemData); } + if ($cart->getData('has_error')) { + throw new GraphQlInputException( + __('Shopping cart error: %message', ['message' => $this->getCartErrors($cart)]) + ); + } + $this->cartRepository->save($cart); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php new file mode 100644 index 0000000000000..947d48f8e15c8 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php @@ -0,0 +1,123 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\CatalogInventory; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; + +class AddProductToCartTest extends GraphQlAbstract +{ + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var QuoteFactory + */ + private $quoteFactory; + + /** + * @var QuoteIdToMaskedQuoteIdInterface + */ + private $quoteIdToMaskedId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->quoteResource = $objectManager->get(QuoteResource::class); + $this->quoteFactory = $objectManager->get(QuoteFactory::class); + $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/products.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @expectedException \Exception + * @expectedExceptionMessage The requested qty is not available + */ + public function testAddProductIfQuantityIsNotAvailable() + { + $sku = 'simple'; + $qty = 200; + + $maskedQuoteId = $this->getMaskedQuoteId(); + $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/products.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @magentoConfigFixture default cataloginventory/item_options/max_sale_qty 5 + * @expectedException \Exception + * @expectedExceptionMessage The most you may purchase is 5. + */ + public function testAddMoreProductsThatAllowed() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/167'); + + $sku = 'custom-design-simple-product'; + $qty = 7; + + $maskedQuoteId = $this->getMaskedQuoteId(); + $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); + $this->graphQlQuery($query); + } + + /** + * @return string + */ + public function getMaskedQuoteId() : string + { + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id'); + + return $this->quoteIdToMaskedId->execute((int)$quote->getId()); + } + + /** + * @param string $maskedQuoteId + * @param string $sku + * @param int $qty + * @return string + */ + public function getAddSimpleProductQuery(string $maskedQuoteId, string $sku, int $qty) : string + { + return <<<QUERY +mutation { + addSimpleProductsToCart( + input: { + cart_id: "{$maskedQuoteId}", + cartItems: [ + { + data: { + qty: $qty + sku: "$sku" + } + } + ] + } + ) { + cart { + cart_id + items { + qty + } + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php index c877ba555039f..36f8af622cfc2 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php @@ -9,10 +9,9 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; -use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteFactory; use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; -use Magento\Config\Model\ResourceModel\Config; class AddSimpleProductToCartTest extends GraphQlAbstract { @@ -22,20 +21,15 @@ class AddSimpleProductToCartTest extends GraphQlAbstract private $quoteResource; /** - * @var Quote + * @var QuoteFactory */ - private $quote; + private $quoteFactory; /** * @var QuoteIdToMaskedQuoteIdInterface */ private $quoteIdToMaskedId; - /** - * @var Config - */ - private $config; - /** * @inheritdoc */ @@ -43,9 +37,8 @@ protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quote = $objectManager->create(Quote::class); + $this->quoteFactory = $objectManager->get(QuoteFactory::class); $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); - $this->config = $objectManager->get(Config::class); } /** @@ -57,45 +50,13 @@ public function testAddSimpleProductsToCart() $sku = 'simple'; $qty = 2; $maskedQuoteId = $this->getMaskedQuoteId(); - $query = $this->getQueryAddSimpleProduct($maskedQuoteId, $sku, $qty); + + $query = $this->geAddSimpleProducttQuery($maskedQuoteId, $sku, $qty); $response = $this->graphQlQuery($query); self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']); - $cartQty = $response['addSimpleProductsToCart']['cart']['items'][0]['qty']; - - $this->assertEquals($qty, $cartQty); - } - - /** - * @magentoApiDataFixture Magento/Catalog/_files/products.php - * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php - * @expectedException \Exception - * @expectedExceptionMessage The requested qty is not available - */ - public function testAddProductIfQuantityIsNotAvailable() - { - $sku = 'simple'; - $qty = 200; - - $maskedQuoteId = $this->getMaskedQuoteId(); - $query = $this->getQueryAddSimpleProduct($maskedQuoteId, $sku, $qty); - $this->graphQlQuery($query); - } - /** - * @magentoApiDataFixture Magento/Catalog/_files/products.php - * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php - * @expectedExceptionMessage The most you may purchase is 5. - */ - public function testAddMoreProductsThatAllowed() - { - $sku = 'custom-design-simple-product'; - $qty = 7; - $maxQty = 5; - - $this->config->saveConfig('cataloginventory/item_options/max_sale_qty', $maxQty, 'default', 0); - $maskedQuoteId = $this->getMaskedQuoteId(); - $query = $this->getQueryAddSimpleProduct($maskedQuoteId, $sku, $qty); - $this->graphQlQuery($query); + $cartQty = $response['addSimpleProductsToCart']['cart']['items'][0]['qty']; + self::assertEquals($qty, $cartQty); } /** @@ -103,22 +64,19 @@ public function testAddMoreProductsThatAllowed() */ public function getMaskedQuoteId() : string { - $this->quoteResource->load( - $this->quote, - 'test_order_1', - 'reserved_order_id' - ); - return $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id'); + + return $this->quoteIdToMaskedId->execute((int)$quote->getId()); } /** * @param string $maskedQuoteId * @param string $sku * @param int $qty - * * @return string */ - public function getQueryAddSimpleProduct(string $maskedQuoteId, string $sku, int $qty) : string + public function geAddSimpleProducttQuery(string $maskedQuoteId, string $sku, int $qty) : string { return <<<QUERY mutation { From 8c2ac6d426d21d2e897bfaa1e7c8b51ff0048e9c Mon Sep 17 00:00:00 2001 From: Nathan Smith <nathsmit@adobe.com> Date: Wed, 27 Feb 2019 16:09:52 -0600 Subject: [PATCH 062/108] MAGETWO-98357: The required product attribute is not displayed when change attribute set - CR feedback --- .../Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml index 73280249eb68d..3027416ee520b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml @@ -46,6 +46,7 @@ <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <deleteData createDataKey="createProductAttribute" stepKey="deleteProductAttribute"/> <deleteData createDataKey="createAttributeSet" stepKey="deleteAttributeSet"/> + <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductsFilter"/> </after> <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> From 06820a34b2a0e1be75e4eb7d02fec0e283d6679f Mon Sep 17 00:00:00 2001 From: Ievgenii Gryshkun <i.gryshkun@gmail.com> Date: Thu, 28 Feb 2019 08:44:38 +0200 Subject: [PATCH 063/108] Fix message when maxSaleQty is set and qty is more than maxSaleQty #367 --- .../Magento/GraphQl/CatalogInventory/AddProductToCartTest.php | 3 --- .../Magento/GraphQl/Quote/AddSimpleProductToCartTest.php | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php index 947d48f8e15c8..4ca1fdf4e8db7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php @@ -61,13 +61,10 @@ public function testAddProductIfQuantityIsNotAvailable() * @magentoApiDataFixture Magento/Catalog/_files/products.php * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php * @magentoConfigFixture default cataloginventory/item_options/max_sale_qty 5 - * @expectedException \Exception * @expectedExceptionMessage The most you may purchase is 5. */ public function testAddMoreProductsThatAllowed() { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/167'); - $sku = 'custom-design-simple-product'; $qty = 7; diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php index 36f8af622cfc2..c1a45f100fde9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php @@ -51,7 +51,7 @@ public function testAddSimpleProductsToCart() $qty = 2; $maskedQuoteId = $this->getMaskedQuoteId(); - $query = $this->geAddSimpleProducttQuery($maskedQuoteId, $sku, $qty); + $query = $this->geAddSimpleProductQuery($maskedQuoteId, $sku, $qty); $response = $this->graphQlQuery($query); self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']); @@ -76,7 +76,7 @@ public function getMaskedQuoteId() : string * @param int $qty * @return string */ - public function geAddSimpleProducttQuery(string $maskedQuoteId, string $sku, int $qty) : string + public function geAddSimpleProductQuery(string $maskedQuoteId, string $sku, int $qty) : string { return <<<QUERY mutation { From 96f6a61d96415931c5b870653fb66babd984372b Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Thu, 28 Feb 2019 09:59:09 +0200 Subject: [PATCH 064/108] Adjusting the namespace --- .../testsuite/Magento/Sales/Controller/Guest/FormTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php index 4ca8b361f0473..1067a474e19aa 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\Sales\Controller; +namespace Magento\Sales\Controller\Guest; use Magento\Customer\Model\Session; use Magento\Framework\Data\Form\FormKey; From e4a1b8ae0d5a7591a41a3f4a31bad98fb81f802b Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Thu, 28 Feb 2019 10:01:20 +0200 Subject: [PATCH 065/108] Merging the PR #21480 --- .../Magento/Sales/Controller/Guest/Form.php | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Sales/Controller/Guest/Form.php b/app/code/Magento/Sales/Controller/Guest/Form.php index 8b4467cb538fa..a1a9f03a20cb8 100644 --- a/app/code/Magento/Sales/Controller/Guest/Form.php +++ b/app/code/Magento/Sales/Controller/Guest/Form.php @@ -4,40 +4,71 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Controller\Guest; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\View\Result\Page; +use Magento\Framework\View\Result\PageFactory; +use Magento\Sales\Helper\Guest as GuestHelper; + +/** + * Class Form + */ class Form extends \Magento\Framework\App\Action\Action { /** - * @var \Magento\Framework\View\Result\PageFactory + * @var PageFactory */ protected $resultPageFactory; /** - * @param \Magento\Framework\App\Action\Context $context - * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory + * @var CustomerSession|null + */ + private $customerSession; + + /** + * @var GuestHelper|null + */ + private $guestHelper; + + /** + * @param Context $context + * @param PageFactory $resultPageFactory + * @param CustomerSession|null $customerSession + * @param GuestHelper|null $guestHelper */ public function __construct( - \Magento\Framework\App\Action\Context $context, - \Magento\Framework\View\Result\PageFactory $resultPageFactory + Context $context, + PageFactory $resultPageFactory, + CustomerSession $customerSession = null, + GuestHelper $guestHelper = null ) { parent::__construct($context); $this->resultPageFactory = $resultPageFactory; + $this->customerSession = $customerSession ?: ObjectManager::getInstance()->get(CustomerSession::class); + $this->guestHelper = $guestHelper ?: ObjectManager::getInstance()->get(GuestHelper::class); } /** * Order view form page * - * @return \Magento\Framework\Controller\Result\Redirect|\Magento\Framework\View\Result\Page + * @return Redirect|Page */ public function execute() { - if ($this->_objectManager->get(\Magento\Customer\Model\Session::class)->isLoggedIn()) { + if ($this->customerSession->isLoggedIn()) { return $this->resultRedirectFactory->create()->setPath('customer/account/'); } + $resultPage = $this->resultPageFactory->create(); $resultPage->getConfig()->getTitle()->set(__('Orders and Returns')); - $this->_objectManager->get(\Magento\Sales\Helper\Guest::class)->getBreadcrumbs($resultPage); + $this->guestHelper->getBreadcrumbs($resultPage); + return $resultPage; } -} +} \ No newline at end of file From f0a98a5a8263cba774d503171e2eb00567365d5b Mon Sep 17 00:00:00 2001 From: nmalevanec <mikola.malevanec@transoftgroup.com> Date: Thu, 28 Feb 2019 10:36:58 +0200 Subject: [PATCH 066/108] Fix static tests. --- .../Magento/Backend/Block/System/Design/Edit/Tab/General.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Backend/Block/System/Design/Edit/Tab/General.php b/app/code/Magento/Backend/Block/System/Design/Edit/Tab/General.php index 3fe187e6e7694..6004d08b4b738 100644 --- a/app/code/Magento/Backend/Block/System/Design/Edit/Tab/General.php +++ b/app/code/Magento/Backend/Block/System/Design/Edit/Tab/General.php @@ -6,6 +6,9 @@ namespace Magento\Backend\Block\System\Design\Edit\Tab; +/** + * General system tab block. + */ class General extends \Magento\Backend\Block\Widget\Form\Generic { /** From b86b78ad69fdd71feb86584e9f308b47bfc9f196 Mon Sep 17 00:00:00 2001 From: Ievgenii Gryshkun <i.gryshkun@gmail.com> Date: Thu, 28 Feb 2019 15:40:04 +0200 Subject: [PATCH 067/108] address_type is null after setting billing and shipping addresses on cart #409 --- .../QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php | 1 + app/code/Magento/QuoteGraphQl/etc/schema.graphqls | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php index 89aa943f9d211..6a86ac6052496 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php @@ -42,6 +42,7 @@ public function execute(QuoteAddress $address): array $addressData = array_merge($addressData, [ 'address_id' => $address->getId(), + 'address_type' => $address->getAddressType(), 'country' => [ 'code' => $address->getCountryId(), 'label' => $address->getCountry() diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index aa4c25a513f67..bcfead583d3af 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -158,7 +158,7 @@ type CartAddress { postcode: String country: CartAddressCountry telephone: String - address_type: AdressTypeEnum + address_type: String available_shipping_methods: [AvailableShippingMethod] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAdress\\AvailableShippingMethods") selected_shipping_method: SelectedShippingMethod items_weight: Float @@ -211,11 +211,6 @@ type SelectedPaymentMethod { type SelectedPaymentMethodAdditionalData { } -enum AdressTypeEnum { - SHIPPING - BILLING -} - type AppliedCoupon { code: String! } From 03afb46ece6e38e0f712a6e45fcfe0e0fc1d5da7 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza <enarc@atwix.com> Date: Thu, 28 Feb 2019 16:15:06 +0100 Subject: [PATCH 068/108] Reverted multiple/checkbox options fix --- .../Model/Cart/AddSimpleProductToCart.php | 45 +++---------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php index b9f2bc88a9cd1..1b32866ed883c 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php @@ -7,8 +7,6 @@ namespace Magento\QuoteGraphQl\Model\Cart; -use Magento\Catalog\Api\Data\ProductCustomOptionInterface; -use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\DataObject; use Magento\Framework\DataObjectFactory; @@ -25,11 +23,6 @@ */ class AddSimpleProductToCart { - /** - * @var ProductCustomOptionRepositoryInterface - */ - private $customOptionRepository; - /** * @var ArrayManager */ @@ -49,18 +42,15 @@ class AddSimpleProductToCart * @param ArrayManager $arrayManager * @param DataObjectFactory $dataObjectFactory * @param ProductRepositoryInterface $productRepository - * @param ProductCustomOptionRepositoryInterface $customOptionRepository */ public function __construct( ArrayManager $arrayManager, DataObjectFactory $dataObjectFactory, - ProductRepositoryInterface $productRepository, - ProductCustomOptionRepositoryInterface $customOptionRepository + ProductRepositoryInterface $productRepository ) { $this->arrayManager = $arrayManager; $this->dataObjectFactory = $dataObjectFactory; $this->productRepository = $productRepository; - $this->customOptionRepository = $customOptionRepository; } /** @@ -77,7 +67,7 @@ public function execute(Quote $cart, array $cartItemData): void { $sku = $this->extractSku($cartItemData); $qty = $this->extractQty($cartItemData); - $customizableOptions = $this->extractCustomizableOptions($cartItemData, $sku); + $customizableOptions = $this->extractCustomizableOptions($cartItemData); try { $product = $this->productRepository->get($sku); @@ -137,23 +127,15 @@ private function extractQty(array $cartItemData): float * Extract Customizable Options from cart item data * * @param array $cartItemData - * @param string $sku * @return array */ - private function extractCustomizableOptions(array $cartItemData, string $sku): array + private function extractCustomizableOptions(array $cartItemData): array { $customizableOptions = $this->arrayManager->get('customizable_options', $cartItemData, []); - $productCustomOptions = $this->customOptionRepository->getList($sku); - $productCustomOptionsMap = $this->getProductCustomOptionsMap($productCustomOptions); - $customizableOptionsData = []; + $customizableOptionsData = []; foreach ($customizableOptions as $customizableOption) { - $multipleOptionTypesList = ['multiple', 'checkbox']; // TODO: use constants - if (in_array($productCustomOptionsMap[$customizableOption['id']]->getType(), $multipleOptionTypesList)) { - $customizableOptionsData[$customizableOption['id']] = explode(',', $customizableOption['value']); - } else { - $customizableOptionsData[$customizableOption['id']] = $customizableOption['value']; - } + $customizableOptionsData[$customizableOption['id']] = $customizableOption['value']; } return $customizableOptionsData; } @@ -174,21 +156,4 @@ private function createBuyRequest(float $qty, array $customOptions): DataObject ], ]); } - - /** - * Creates an array with a key equals option ID - * - * @param array $productCustomOptions - * @return array - */ - private function getProductCustomOptionsMap(array $productCustomOptions): array - { - $customOptionsData = []; - /** @var ProductCustomOptionInterface $productCustomOption */ - foreach ($productCustomOptions as $productCustomOption) { - $customOptionsData[$productCustomOption->getOptionId()] = $productCustomOption; - } - - return $customOptionsData; - } } From 8df5cab84414495b4f4ac7b2ce91f63afb215dee Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza <enarc@atwix.com> Date: Thu, 28 Feb 2019 16:17:45 +0100 Subject: [PATCH 069/108] Typo fix --- app/code/Magento/CatalogGraphQl/etc/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index d4b724b342acf..778d0783305b7 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -323,7 +323,7 @@ type CustomizableDropDownValue @doc(description: "CustomizableDropDownValue defi sort_order: Int @doc(description: "The order in which the option is displayed") } -type CustomizableMultipleOption implements CustomizableOptionInterface @doc(description: "CustomizableMultipoleOption contains information about a multiselect that is defined as part of a customizable option") { +type CustomizableMultipleOption implements CustomizableOptionInterface @doc(description: "CustomizableMultipleOption contains information about a multiselect that is defined as part of a customizable option") { value: [CustomizableMultipleValue] @doc(description: "An array that defines the set of options for a multiselect") } From e9a9659c824a64343759fe98347c6e0ec2f288cf Mon Sep 17 00:00:00 2001 From: Vitaliy Honcharenko <vgoncharenko@magento.com> Date: Thu, 28 Feb 2019 09:53:15 -0600 Subject: [PATCH 070/108] MAGETWO-95294: Mysql search slow on the catalog page - changes after CR --- .../Model/ResourceModel/Advanced/Collection.php | 2 ++ .../Model/ResourceModel/Fulltext/Collection.php | 11 ++++++++++- .../Fulltext/Collection/SearchCriteriaResolver.php | 2 +- .../Magento/Catalog/Model/Layer/CategoryTest.php | 3 +-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php index fe6a9421604f5..0dbcf5f8cf375 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php @@ -266,6 +266,7 @@ public function setOrder($attribute, $dir = Select::SQL_DESC) */ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) { + /** This changes need in BIC reasons for support dynamic improved algorithm for price aggregation process. */ if ($this->isCurrentEngineMysql()) { parent::addCategoryFilter($category); } else { @@ -281,6 +282,7 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) */ public function setVisibility($visibility) { + /** This changes need in BIC reasons for support dynamic improved algorithm for price aggregation process. */ if ($this->isCurrentEngineMysql()) { parent::setVisibility($visibility); } else { diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index 4a03dea1530db..ecc25a55a10d7 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -584,7 +584,12 @@ public function getFacetedData($field) public function addCategoryFilter(\Magento\Catalog\Model\Category $category) { $this->addFieldToFilter('category_ids', $category->getId()); - $this->_productLimitationPrice(); + /** This changes need in BIC reasons for support dynamic improved algorithm for price aggregation process. */ + if ($this->isCurrentEngineMysql()) { + parent::addCategoryFilter($category); + } else { + $this->_productLimitationPrice(); + } return $this; } @@ -598,6 +603,10 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) public function setVisibility($visibility) { $this->addFieldToFilter('visibility', $visibility); + /** This changes need in BIC reasons for support dynamic improved algorithm for price aggregation process. */ + if ($this->isCurrentEngineMysql()) { + parent::setVisibility($visibility); + } return $this; } diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php index 6ffdf07e7be82..255c7885e84b9 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php @@ -61,7 +61,7 @@ public function __construct( string $searchRequestName, int $currentPage, int $size, - array $orders + ?array $orders ) { $this->builder = $builder; $this->collection = $collection; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/CategoryTest.php index 5721982ab8dc3..d4926e78040d6 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/CategoryTest.php @@ -38,8 +38,7 @@ public function testGetProductCollection() /** @var $collection \Magento\Catalog\Model\ResourceModel\Product\Collection */ $collection = $this->_model->getProductCollection(); $this->assertInstanceOf(\Magento\Catalog\Model\ResourceModel\Product\Collection::class, $collection); - $ids = $collection->getAllIds(); - $this->assertEquals(2, count($ids)); + $this->assertEquals(2, $collection->count()); $this->assertSame($collection, $this->_model->getProductCollection()); } From 44cb4d3677627a83ddd2269839556a6459e6f31e Mon Sep 17 00:00:00 2001 From: Vitaliy Honcharenko <vgoncharenko@magento.com> Date: Thu, 28 Feb 2019 11:24:32 -0600 Subject: [PATCH 071/108] MAGETWO-95294: Mysql search slow on the catalog page - changes after CR --- .../view/frontend/templates/advanced/result.phtml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml index 6bdca48f499c3..3f616ab791dfe 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml +++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml @@ -11,6 +11,8 @@ /** * @var $block \Magento\CatalogSearch\Block\Advanced\Result */ +/** this changes need for valid apply filters and configuration before search process is started */ +$productList = $block->getProductListHtml(); ?> <?php if ($results = $block->getResultCount()): ?> <div class="search found"> @@ -49,6 +51,6 @@ </div> <?php endif; ?> <?php if ($block->getResultCount()): ?> - <div class="search results"><?= /* @escapeNotVerified */ $block->getProductListHtml() ?></div> + <div class="search results"><?= /* @escapeNotVerified */ $productList ?></div> <?php endif; ?> <?php $block->getSearchCriterias(); ?> From cc65c8227c77bf7ec14b7376c4151cfdd9a13569 Mon Sep 17 00:00:00 2001 From: Mastiuhin Olexandr <mastiuhin.olexandr@transoftgroup.com> Date: Thu, 28 Feb 2019 20:27:19 +0200 Subject: [PATCH 072/108] MAGETWO-98088: [2.3] Configurable product can be added as a variation to another Configurable product in the admin --- .../Unit/Ui/Component/ColumnFactoryTest.php | 156 ++++++++++++++++++ .../Catalog/Ui/Component/ColumnFactory.php | 6 +- .../Modifier/Data/AssociatedProductsTest.php | 64 ++++++- 3 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php new file mode 100644 index 0000000000000..b4945cf65096b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php @@ -0,0 +1,156 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Test\Unit\Ui\Component; + +use PHPUnit\Framework\TestCase; +use Magento\Catalog\Ui\Component\ColumnFactory; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Framework\View\Element\UiComponentFactory; +use Magento\Ui\Component\Listing\Columns\ColumnInterface; +use Magento\Ui\Component\Filters\FilterModifier; + +/** + * ColumnFactory test. + */ +class ColumnFactoryTest extends TestCase +{ + /** + * @var ColumnFactory + */ + private $columnFactory; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var ProductAttributeInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $attribute; + + /** + * @var ContextInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $context; + + /** + * @var UiComponentFactory|\PHPUnit\Framework\MockObject\MockObject + */ + private $uiComponentFactory; + + /** + * @var ColumnInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $column; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->objectManager = new ObjectManager($this); + + $this->attribute = $this->getMockBuilder(ProductAttributeInterface::class) + ->setMethods(['usesSource']) + ->getMockForAbstractClass(); + $this->context = $this->createMock(ContextInterface::class); + $this->uiComponentFactory = $this->createMock(UiComponentFactory::class); + $this->column = $this->getMockForAbstractClass(ColumnInterface::class); + $this->uiComponentFactory->method('create') + ->willReturn($this->column); + + $this->columnFactory = $this->objectManager->getObject(ColumnFactory::class, [ + 'componentFactory' => $this->uiComponentFactory + ]); + } + + /** + * Tests the create method will return correct object. + * + * @return void + */ + public function testCreatedObject(): void + { + $this->context->method('getRequestParam') + ->with(FilterModifier::FILTER_MODIFIER, []) + ->willReturn([]); + + $object = $this->columnFactory->create($this->attribute, $this->context); + $this->assertEquals( + $this->column, + $object, + 'Object must be the same which the ui component factory creates.' + ); + } + + /** + * Tests create method with not filterable in grid attribute. + * + * @param array $filterModifiers + * @param null|string $filter + * + * @return void + * @dataProvider filterModifiersProvider + */ + public function testCreateWithNotFilterableInGridAttribute(array $filterModifiers,?string $filter): void + { + $componentFactoryArgument = [ + 'data' => [ + 'config' => [ + 'label' => __(null), + 'dataType' => 'text', + 'add_field' => true, + 'visible' => null, + 'filter' => $filter, + 'component' => 'Magento_Ui/js/grid/columns/column', + ], + ], + 'context' => $this->context, + ]; + + $this->context->method('getRequestParam') + ->with(FilterModifier::FILTER_MODIFIER, []) + ->willReturn($filterModifiers); + $this->attribute->method('getIsFilterableInGrid') + ->willReturn(false); + $this->attribute->method('getAttributeCode') + ->willReturn('color'); + + $this->uiComponentFactory->expects($this->once()) + ->method('create') + ->with($this->anything(), $this->anything(), $componentFactoryArgument); + + $this->columnFactory->create($this->attribute, $this->context); + } + + /** + * The omit filterable in grid parameter data provider. + * + * @return array + */ + public function filterModifiersProvider(): array + { + return [ + 'without' => [ + 'filter_modifiers' => [], + 'filter' => null, + ], + 'with' => [ + 'filter_modifiers' => [ + 'color' => [ + 'condition_type' => 'notnull', + ], + ], + 'filter' => 'text', + ], + ]; + } +} diff --git a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php index 1903bcd144831..ea6b1fd47a0a5 100644 --- a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php +++ b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php @@ -5,6 +5,8 @@ */ namespace Magento\Catalog\Ui\Component; +use Magento\Ui\Component\Filters\FilterModifier; + /** * Column Factory * @@ -60,13 +62,15 @@ public function __construct(\Magento\Framework\View\Element\UiComponentFactory $ */ public function create($attribute, $context, array $config = []) { + $filterModifiers = $context->getRequestParam(FilterModifier::FILTER_MODIFIER, []); + $columnName = $attribute->getAttributeCode(); $config = array_merge([ 'label' => __($attribute->getDefaultFrontendLabel()), 'dataType' => $this->getDataType($attribute), 'add_field' => true, 'visible' => $attribute->getIsVisibleInGrid(), - 'filter' => ($attribute->getIsFilterableInGrid()) + 'filter' => ($attribute->getIsFilterableInGrid() || array_key_exists($columnName, $filterModifiers)) ? $this->getFilterType($attribute->getFrontendInput()) : null, ], $config); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php index 234f0aae6a3ea..a76cc02a8377a 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php @@ -5,7 +5,17 @@ */ namespace Magento\ConfigurableProduct\Ui\DataProvider\Product\Form\Modifier\Data; -class AssociatedProductsTest extends \PHPUnit\Framework\TestCase +use Magento\Framework\View\Element\UiComponentFactory; +use Magento\Ui\Component\Filters\FilterModifier; +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\ConfigurableProduct\Ui\DataProvider\Product\Form\Modifier\ConfigurablePanel; +use Magento\Framework\App\RequestInterface; +use PHPUnit\Framework\TestCase; + +/** + * AssociatedProductsTest + */ +class AssociatedProductsTest extends TestCase { /** * @var \Magento\Framework\ObjectManagerInterface $objectManager @@ -17,7 +27,10 @@ class AssociatedProductsTest extends \PHPUnit\Framework\TestCase */ private $registry; - public function setUp() + /** + * @inheritdoc + */ + public function setUp(): void { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $this->registry = $this->objectManager->get(\Magento\Framework\Registry::class); @@ -64,6 +77,53 @@ public function testGetProductMatrix($interfaceLocale) } } + /** + * Tests configurable product won't appear in product listing. + * + * Tests configurable product won't appear in configurable associated product listing if its options attribute + * is not filterable in grid. + * + * @return void + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoAppArea adminhtml + */ + public function testAddManuallyConfigurationsWithNotFilterableInGridAttribute(): void + { + /** @var RequestInterface $request */ + $request = $this->objectManager->get(RequestInterface::class); + $request->setParams([ + FilterModifier::FILTER_MODIFIER => [ + 'test_configurable' => [ + 'condition_type' => 'notnull', + ], + ], + 'attributes_codes' => [ + 'test_configurable' + ], + ]); + $context = $this->objectManager->create(ContextInterface::class, ['request' => $request]); + /** @var UiComponentFactory $uiComponentFactory */ + $uiComponentFactory = $this->objectManager->get(UiComponentFactory::class); + $uiComponent = $uiComponentFactory->create( + ConfigurablePanel::ASSOCIATED_PRODUCT_LISTING, + null, + ['context' => $context] + ); + + foreach ($uiComponent->getChildComponents() as $childUiComponent) { + $childUiComponent->prepare(); + } + + $dataSource = $uiComponent->getDataSourceData(); + $skus = array_column($dataSource['data']['items'], 'sku'); + + $this->assertNotContains( + 'configurable', + $skus, + 'Only products with specified attribute should be in product list' + ); + } + /** * @return array */ From cfcb7739bf5e56628e6cc9f806212bb1e448d855 Mon Sep 17 00:00:00 2001 From: vtymchynskyi <vtymchynskyi@magento.com> Date: Wed, 27 Feb 2019 15:46:58 -0600 Subject: [PATCH 073/108] MAGETWO-96138: Transfer Cart Line Items and Transfer Shipping Options do not work for PayPal - Fixed access to shipping options callback controller for PayPal --- .../ShippingOptionsCallback.php | 29 ++++++++++++++++++- .../Magento/Paypal/Model/Express/Checkout.php | 1 + .../Paypal/Model/Express/CheckoutTest.php | 11 +++++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/ShippingOptionsCallback.php b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/ShippingOptionsCallback.php index cb1b3388dc06a..fc3a45e1e1397 100644 --- a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/ShippingOptionsCallback.php +++ b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/ShippingOptionsCallback.php @@ -6,7 +6,17 @@ */ namespace Magento\Paypal\Controller\Express\AbstractExpress; -class ShippingOptionsCallback extends \Magento\Paypal\Controller\Express\AbstractExpress +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Paypal\Controller\Express\AbstractExpress; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; + +/** + * Returns shipping rates by server-to-server request from PayPal. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ShippingOptionsCallback extends AbstractExpress implements CsrfAwareActionInterface { /** * @var \Magento\Quote\Api\CartRepositoryInterface @@ -65,4 +75,21 @@ public function execute() $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); } } + + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } } diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php index 38ba0983514b0..72f166e8d07c1 100644 --- a/app/code/Magento/Paypal/Model/Express/Checkout.php +++ b/app/code/Magento/Paypal/Model/Express/Checkout.php @@ -1076,6 +1076,7 @@ protected static function cmpShippingOptions(DataObject $option1, DataObject $op */ protected function _matchShippingMethodCode(Address $address, $selectedCode) { + $address->collectShippingRates(); $options = $this->_prepareShippingOptions($address, false); foreach ($options as $option) { if ($selectedCode === $option['code'] // the proper case as outlined in documentation 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 cb83fa3abd857..3f7f8719fd587 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php @@ -72,7 +72,7 @@ protected function setUp() $this->api = $this->getMockBuilder(Nvp::class) ->disableOriginalConstructor() - ->setMethods(['call', 'getExportedShippingAddress', 'getExportedBillingAddress']) + ->setMethods(['call', 'getExportedShippingAddress', 'getExportedBillingAddress', 'getShippingRateCode']) ->getMock(); $this->api->expects($this->any()) @@ -302,6 +302,8 @@ public function testReturnFromPaypal() public function testReturnFromPaypalButton() { $quote = $this->getFixtureQuote(); + $quote->getShippingAddress()->setShippingMethod(''); + $this->prepareCheckoutModel($quote); $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 1); @@ -317,6 +319,8 @@ public function testReturnFromPaypalButton() $this->assertEquals($exportedShippingData['telephone'], $shippingAddress->getTelephone()); $this->assertEquals($exportedShippingData['email'], $shippingAddress->getEmail()); + $this->assertEquals('flatrate_flatrate', $shippingAddress->getShippingMethod()); + $this->assertEquals([$exportedShippingData['street']], $billingAddress->getStreet()); $this->assertEquals($exportedShippingData['firstname'], $billingAddress->getFirstname()); $this->assertEquals($exportedShippingData['city'], $billingAddress->getCity()); @@ -551,6 +555,9 @@ private function prepareCheckoutModel(Quote $quote, $prefix = '') $this->api->method('getExportedShippingAddress') ->willReturn($exportedShippingAddress); + $this->api->method('getShippingRateCode') + ->willReturn('flatrate_flatrate Flat Rate - Fixed'); + $this->paypalInfo->method('importToPayment') ->with($this->api, $quote->getPayment()); } @@ -573,7 +580,7 @@ private function getExportedData(): array 'city' => 'Denver', 'street' => '66 Pearl St', 'postcode' => '80203', - 'telephone' => '555-555-555' + 'telephone' => '555-555-555', ], 'billing' => [ 'email' => 'customer@example.com', From 832bb705767eca78f0d675a7c7c3149be524fafd Mon Sep 17 00:00:00 2001 From: nmalevanec <mikola.malevanec@transoftgroup.com> Date: Fri, 1 Mar 2019 11:41:43 +0200 Subject: [PATCH 074/108] Fix static tests. --- app/code/Magento/Sales/Controller/Guest/Form.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Sales/Controller/Guest/Form.php b/app/code/Magento/Sales/Controller/Guest/Form.php index a1a9f03a20cb8..04bb66f3d5b6e 100644 --- a/app/code/Magento/Sales/Controller/Guest/Form.php +++ b/app/code/Magento/Sales/Controller/Guest/Form.php @@ -10,6 +10,7 @@ use Magento\Customer\Model\Session as CustomerSession; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Controller\Result\Redirect; use Magento\Framework\View\Result\Page; @@ -19,7 +20,7 @@ /** * Class Form */ -class Form extends \Magento\Framework\App\Action\Action +class Form extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface { /** * @var PageFactory @@ -71,4 +72,4 @@ public function execute() return $resultPage; } -} \ No newline at end of file +} From 970325f23c48fa7e347f5ba7f4131831fff73a82 Mon Sep 17 00:00:00 2001 From: Mastiuhin Olexandr <mastiuhin.olexandr@transoftgroup.com> Date: Fri, 1 Mar 2019 12:05:37 +0200 Subject: [PATCH 075/108] MAGETWO-98088: [2.3] Configurable product can be added as a variation to another Configurable product in the admin --- .../Product/Form/Modifier/Data/AssociatedProductsTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php index a76cc02a8377a..676433c0a1e6c 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php @@ -14,6 +14,8 @@ /** * AssociatedProductsTest + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AssociatedProductsTest extends TestCase { From f9eccc8010502d6b19a5ef53f6ef0275b461683e Mon Sep 17 00:00:00 2001 From: Mastiuhin Olexandr <mastiuhin.olexandr@transoftgroup.com> Date: Fri, 1 Mar 2019 12:44:57 +0200 Subject: [PATCH 076/108] MAGETWO-98088: [2.3] Configurable product can be added as a variation to another Configurable product in the admin --- .../Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php index b4945cf65096b..8a6f61c71e799 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php @@ -100,7 +100,7 @@ public function testCreatedObject(): void * @return void * @dataProvider filterModifiersProvider */ - public function testCreateWithNotFilterableInGridAttribute(array $filterModifiers,?string $filter): void + public function testCreateWithNotFilterableInGridAttribute(array $filterModifiers, ?string $filter): void { $componentFactoryArgument = [ 'data' => [ From 7190983a7aa4c4c09d997871a4d71d863e3872f0 Mon Sep 17 00:00:00 2001 From: nmalevanec <mikola.malevanec@transoftgroup.com> Date: Fri, 1 Mar 2019 13:34:39 +0200 Subject: [PATCH 077/108] Fix static tests. --- .../MediaStorage/Service/ImageResize.php | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/MediaStorage/Service/ImageResize.php b/app/code/Magento/MediaStorage/Service/ImageResize.php index faefd40279084..aae90512b3d95 100644 --- a/app/code/Magento/MediaStorage/Service/ImageResize.php +++ b/app/code/Magento/MediaStorage/Service/ImageResize.php @@ -24,6 +24,8 @@ use Magento\Framework\App\Filesystem\DirectoryList; /** + * Image resize service. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ImageResize @@ -123,7 +125,8 @@ public function __construct( } /** - * Create resized images of different sizes from an original image + * Create resized images of different sizes from an original image. + * * @param string $originalImageName * @throws NotFoundException */ @@ -141,7 +144,8 @@ public function resizeFromImageName(string $originalImageName) } /** - * Create resized images of different sizes from themes + * Create resized images of different sizes from themes. + * * @param array|null $themes * @return \Generator * @throws NotFoundException @@ -169,7 +173,8 @@ public function resizeFromThemes(array $themes = null): \Generator } /** - * Search the current theme + * Search the current theme. + * * @return array */ private function getThemesInUse(): array @@ -187,7 +192,8 @@ private function getThemesInUse(): array } /** - * Get view images data from themes + * Get view images data from themes. + * * @param array $themes * @return array */ @@ -211,7 +217,8 @@ private function getViewImages(array $themes): array } /** - * Get unique image index + * Get unique image index. + * * @param array $imageData * @return string */ @@ -223,7 +230,8 @@ private function getUniqueImageIndex(array $imageData): string } /** - * Make image + * Make image. + * * @param string $originalImagePath * @param array $imageParams * @return Image @@ -241,7 +249,8 @@ private function makeImage(string $originalImagePath, array $imageParams): Image } /** - * Resize image + * Resize image. + * * @param array $viewImage * @param string $originalImagePath * @param string $originalImageName From 28a9720406a0d0f22e0b35c6d4e99efb456df9c9 Mon Sep 17 00:00:00 2001 From: nmalevanec <mikola.malevanec@transoftgroup.com> Date: Fri, 1 Mar 2019 15:26:43 +0200 Subject: [PATCH 078/108] Fix static tests. --- app/code/Magento/Robots/Model/Config/Value.php | 1 + app/code/Magento/Sitemap/Model/Sitemap.php | 3 +++ 2 files changed, 4 insertions(+) diff --git a/app/code/Magento/Robots/Model/Config/Value.php b/app/code/Magento/Robots/Model/Config/Value.php index 5ccfa12334607..16a5a486e1078 100644 --- a/app/code/Magento/Robots/Model/Config/Value.php +++ b/app/code/Magento/Robots/Model/Config/Value.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Robots\Model\Config; use Magento\Framework\App\Cache\TypeListInterface; diff --git a/app/code/Magento/Sitemap/Model/Sitemap.php b/app/code/Magento/Sitemap/Model/Sitemap.php index 01c4f4186cf48..c35e20d997d85 100644 --- a/app/code/Magento/Sitemap/Model/Sitemap.php +++ b/app/code/Magento/Sitemap/Model/Sitemap.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Sitemap\Model; use Magento\Config\Model\Config\Reader\Source\Deployed\DocumentRoot; @@ -15,6 +16,8 @@ use Magento\Sitemap\Model\ResourceModel\Sitemap as SitemapResource; /** + * Sitemap model. + * * @method string getSitemapType() * @method \Magento\Sitemap\Model\Sitemap setSitemapType(string $value) * @method string getSitemapFilename() From 34a37cb6bfcd51fcca7403124058b6be94d46bd9 Mon Sep 17 00:00:00 2001 From: Mastiuhin Olexandr <mastiuhin.olexandr@transoftgroup.com> Date: Fri, 1 Mar 2019 16:14:10 +0200 Subject: [PATCH 079/108] MAGETWO-98088: [2.3] Configurable product can be added as a variation to another Configurable product in the admin --- .../Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php index 8a6f61c71e799..774edcfeb6b64 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php @@ -132,7 +132,7 @@ public function testCreateWithNotFilterableInGridAttribute(array $filterModifier } /** - * The omit filterable in grid parameter data provider. + * Filter modifiers data provider. * * @return array */ From 23f415a5d7f7f032a33f75ced47a76b4b422ef7e Mon Sep 17 00:00:00 2001 From: Maksym Novik <m.novik@ism-ukraine.com> Date: Fri, 1 Mar 2019 17:02:10 +0200 Subject: [PATCH 080/108] Static tests: forbid 'or' instead of '||' #21062. Added Squiz.Operators.ValidLogicalOperators rule --- dev/tests/static/framework/Magento/ruleset.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/static/framework/Magento/ruleset.xml b/dev/tests/static/framework/Magento/ruleset.xml index d0f0d38f8fa03..9dde4b7bd7d32 100644 --- a/dev/tests/static/framework/Magento/ruleset.xml +++ b/dev/tests/static/framework/Magento/ruleset.xml @@ -84,5 +84,6 @@ <rule ref="Squiz.Commenting.DocCommentAlignment"/> <rule ref="Squiz.Functions.GlobalFunction"/> + <rule ref="Squiz.Operators.ValidLogicalOperators"/> <rule ref="Squiz.WhiteSpace.LogicalOperatorSpacing"/> </ruleset> From 1b5991d67c14599df9bab55c13a80eb4d42d4d6f Mon Sep 17 00:00:00 2001 From: Valerii Naida <vnayda@adobe.com> Date: Fri, 1 Mar 2019 09:47:27 -0600 Subject: [PATCH 081/108] GraphQL-293: When maxSaleQty is set and qty is more than maxSaleQty the "The most you may purchase is %1." message should be sent instead of "Internal server error" --- .../CatalogInventory/AddProductToCartTest.php | 6 ++- .../Quote/AddSimpleProductToCartTest.php | 54 +++++++++++++------ 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php index 4ca1fdf4e8db7..17c2af8dc59d0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php @@ -55,22 +55,27 @@ public function testAddProductIfQuantityIsNotAvailable() $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); $this->graphQlQuery($query); + self::fail('Should be "The requested qty is not available" error message.'); } /** * @magentoApiDataFixture Magento/Catalog/_files/products.php * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php * @magentoConfigFixture default cataloginventory/item_options/max_sale_qty 5 + * @expectedException \Exception * @expectedExceptionMessage The most you may purchase is 5. */ public function testAddMoreProductsThatAllowed() { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/167'); + $sku = 'custom-design-simple-product'; $qty = 7; $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); $this->graphQlQuery($query); + self::fail('Should be "The most you may purchase is 5." error message.'); } /** @@ -108,7 +113,6 @@ public function getAddSimpleProductQuery(string $maskedQuoteId, string $sku, int } ) { cart { - cart_id items { qty } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php index d5bdb942e9b2c..3bda57381e9d1 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php @@ -9,7 +9,7 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; -use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteFactory; use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; @@ -21,9 +21,9 @@ class AddSimpleProductToCartTest extends GraphQlAbstract private $quoteResource; /** - * @var Quote + * @var QuoteFactory */ - private $quote; + private $quoteFactory; /** * @var QuoteIdToMaskedQuoteIdInterface @@ -37,29 +37,48 @@ protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quote = $objectManager->create(Quote::class); + $this->quoteFactory = $objectManager->get(QuoteFactory::class); $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); } /** * @magentoApiDataFixture Magento/Catalog/_files/products.php * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php - * @expectedException \Exception - * @expectedExceptionMessage The requested qty is not available */ - public function testAddProductIfQuantityIsNotAvailable() + public function testAddSimpleProductToCart() { $sku = 'simple'; - $qty = 200; + $qty = 2; + $maskedQuoteId = $this->getMaskedQuoteId(); - $this->quoteResource->load( - $this->quote, - 'test_order_1', - 'reserved_order_id' - ); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']); - $query = <<<QUERY + self::assertEquals($qty, $response['addSimpleProductsToCart']['cart']['items'][0]['qty']); + self::assertEquals($sku, $response['addSimpleProductsToCart']['cart']['items'][0]['product']['sku']); + } + + /** + * @return string + */ + public function getMaskedQuoteId() : string + { + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id'); + + return $this->quoteIdToMaskedId->execute((int)$quote->getId()); + } + + /** + * @param string $maskedQuoteId + * @param string $sku + * @param int $qty + * @return string + */ + public function getAddSimpleProductQuery(string $maskedQuoteId, string $sku, int $qty): string + { + return <<<QUERY mutation { addSimpleProductsToCart( input: { @@ -77,12 +96,13 @@ public function testAddProductIfQuantityIsNotAvailable() cart { items { qty + product { + sku + } } } } } QUERY; - - $this->graphQlQuery($query); } } From 3e0c7e7c2227549bfcc663bb1d37d3a49d7fed31 Mon Sep 17 00:00:00 2001 From: Romain Ruaud <romain.ruaud@smile.fr> Date: Tue, 26 Feb 2019 11:58:10 +0100 Subject: [PATCH 082/108] Elasticsearch6 implementation. --- .../AdvancedSearch/etc/adminhtml/system.xml | 6 +- .../System/Config/TestConnection.php | 32 + app/code/Magento/Elasticsearch6/LICENSE.txt | 48 ++ .../Magento/Elasticsearch6/LICENSE_AFL.txt | 48 ++ .../FieldName/Resolver/DefaultResolver.php | 34 ++ .../Model/Client/Elasticsearch.php | 332 +++++++++++ .../Magento/Elasticsearch6/Model/Config.php | 57 ++ .../Model/DataProvider/Suggestions.php | 271 +++++++++ app/code/Magento/Elasticsearch6/README.md | 2 + .../Resolver/DefaultResolverTest.php | 118 ++++ .../Unit/Model/Client/ElasticsearchTest.php | 562 ++++++++++++++++++ .../Model/DataProvider/SuggestionsTest.php | 183 ++++++ app/code/Magento/Elasticsearch6/composer.json | 26 + .../Elasticsearch6/etc/adminhtml/system.xml | 85 +++ .../Magento/Elasticsearch6/etc/config.xml | 20 + app/code/Magento/Elasticsearch6/etc/di.xml | 165 +++++ .../Magento/Elasticsearch6/etc/module.xml | 17 + .../Magento/Elasticsearch6/registration.php | 11 + 18 files changed, 2014 insertions(+), 3 deletions(-) create mode 100644 app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php create mode 100644 app/code/Magento/Elasticsearch6/LICENSE.txt create mode 100644 app/code/Magento/Elasticsearch6/LICENSE_AFL.txt create mode 100644 app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php create mode 100644 app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php create mode 100644 app/code/Magento/Elasticsearch6/Model/Config.php create mode 100644 app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php create mode 100644 app/code/Magento/Elasticsearch6/README.md create mode 100644 app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php create mode 100644 app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php create mode 100644 app/code/Magento/Elasticsearch6/Test/Unit/Model/DataProvider/SuggestionsTest.php create mode 100644 app/code/Magento/Elasticsearch6/composer.json create mode 100644 app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml create mode 100644 app/code/Magento/Elasticsearch6/etc/config.xml create mode 100644 app/code/Magento/Elasticsearch6/etc/di.xml create mode 100644 app/code/Magento/Elasticsearch6/etc/module.xml create mode 100644 app/code/Magento/Elasticsearch6/registration.php diff --git a/app/code/Magento/AdvancedSearch/etc/adminhtml/system.xml b/app/code/Magento/AdvancedSearch/etc/adminhtml/system.xml index fa7774f5cec1d..2c4f7fca1834b 100644 --- a/app/code/Magento/AdvancedSearch/etc/adminhtml/system.xml +++ b/app/code/Magento/AdvancedSearch/etc/adminhtml/system.xml @@ -48,18 +48,18 @@ </depends> </field> <!--<group id="suggestions">--> - <field id="search_suggestion_enabled" translate="label comment" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="search_suggestion_enabled" translate="label comment" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Enable Search Suggestions</label> <comment>When you enable this option your site may slow down.</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="search_suggestion_count" translate="label" type="text" sortOrder="71" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="search_suggestion_count" translate="label" type="text" sortOrder="91" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Search Suggestions Count</label> <depends> <field id="search_suggestion_enabled">1</field> </depends> </field> - <field id="search_suggestion_count_results_enabled" translate="label" type="select" sortOrder="72" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="search_suggestion_count_results_enabled" translate="label" type="select" sortOrder="92" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Show Results Count for Each Suggestion</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>When you enable this option your site may slow down.</comment> diff --git a/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php b/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php new file mode 100644 index 0000000000000..5573d959fa372 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Elasticsearch6\Block\Adminhtml\System\Config; + +/** + * Elasticsearch 6x test connection block + * @codeCoverageIgnore + */ +class TestConnection extends \Magento\AdvancedSearch\Block\Adminhtml\System\Config\TestConnection +{ + /** + * {@inheritdoc} + */ + protected function _getFieldMapping() + { + $fields = [ + 'engine' => 'catalog_search_engine', + 'hostname' => 'catalog_search_elasticsearch6_server_hostname', + 'port' => 'catalog_search_elasticsearch6_server_port', + 'index' => 'catalog_search_elasticsearch6_index_prefix', + 'enableAuth' => 'catalog_search_elasticsearch6_enable_auth', + 'username' => 'catalog_search_elasticsearch6_username', + 'password' => 'catalog_search_elasticsearch6_password', + 'timeout' => 'catalog_search_elasticsearch6_server_timeout', + ]; + + return array_merge(parent::_getFieldMapping(), $fields); + } +} diff --git a/app/code/Magento/Elasticsearch6/LICENSE.txt b/app/code/Magento/Elasticsearch6/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/Elasticsearch6/LICENSE.txt @@ -0,0 +1,48 @@ + +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 <insert your license name here>" 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. \ No newline at end of file diff --git a/app/code/Magento/Elasticsearch6/LICENSE_AFL.txt b/app/code/Magento/Elasticsearch6/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +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 <insert your license name here>" 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/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php b/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php new file mode 100644 index 0000000000000..2420335a95dc5 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch6\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver; + +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter; + +/** + * Default name resolver. + */ +class DefaultResolver extends \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver +{ + /** + * Get field name. + * + * @param AttributeAdapter $attribute + * @param array $context + * @return string + */ + public function getFieldName(AttributeAdapter $attribute, $context = []): ?string + { + $fieldName = parent::getFieldName($attribute, $context); + + if ($fieldName === '_all') { + $fieldName = '_search'; + } + + return $fieldName; + } +} diff --git a/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php new file mode 100644 index 0000000000000..af39b24acda56 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php @@ -0,0 +1,332 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Elasticsearch6\Model\Client; + +use Magento\Framework\Exception\LocalizedException; +use Magento\AdvancedSearch\Model\Client\ClientInterface; + +/** + * Elasticsearch client + */ +class Elasticsearch implements ClientInterface +{ + /** + * Elasticsearch Client instances + * + * @var \Elasticsearch\Client[] + */ + private $client; + + /** + * @var array + */ + private $clientOptions; + + /** + * @var bool + */ + private $pingResult; + + /** + * Initialize Elasticsearch Client + * + * @param array $options + * @param \Elasticsearch\Client|null $elasticsearchClient + * @throws LocalizedException + */ + public function __construct( + $options = [], + $elasticsearchClient = null + ) { + if (empty($options['hostname']) || ((!empty($options['enableAuth']) && + ($options['enableAuth'] == 1)) && (empty($options['username']) || empty($options['password'])))) { + throw new LocalizedException( + __('The search failed because of a search engine misconfiguration.') + ); + } + + if (!($elasticsearchClient instanceof \Elasticsearch\Client)) { + $config = $this->buildConfig($options); + $elasticsearchClient = \Elasticsearch\ClientBuilder::fromConfig($config, true); + } + $this->client[getmypid()] = $elasticsearchClient; + $this->clientOptions = $options; + } + + /** + * Get Elasticsearch Client + * + * @return \Elasticsearch\Client + */ + private function getClient() + { + $pid = getmypid(); + if (!isset($this->client[$pid])) { + $config = $this->buildConfig($this->clientOptions); + $this->client[$pid] = \Elasticsearch\ClientBuilder::fromConfig($config, true); + } + return $this->client[$pid]; + } + + /** + * Ping the Elasticsearch client + * + * @return bool + */ + public function ping() + { + if ($this->pingResult === null) { + $this->pingResult = $this->getClient()->ping(['client' => ['timeout' => $this->clientOptions['timeout']]]); + } + + return $this->pingResult; + } + + /** + * Validate connection params + * + * @return bool + */ + public function testConnection() + { + return $this->ping(); + } + + /** + * Build config. + * + * @param array $options + * @return array + */ + private function buildConfig($options = []) + { + $host = preg_replace('/http[s]?:\/\//i', '', $options['hostname']); + $protocol = parse_url($options['hostname'], PHP_URL_SCHEME); + if (!$protocol) { + $protocol = 'http'; + } + if (!empty($options['port'])) { + $host .= ':' . $options['port']; + } + if (!empty($options['enableAuth']) && ($options['enableAuth'] == 1)) { + $host = sprintf('%s://%s:%s@%s', $protocol, $options['username'], $options['password'], $host); + } + + $options['hosts'] = [$host]; + return $options; + } + + /** + * Performs bulk query over Elasticsearch index + * + * @param array $query + * @return void + */ + public function bulkQuery($query) + { + $this->getClient()->bulk($query); + } + + /** + * Creates an Elasticsearch index. + * + * @param string $index + * @param array $settings + * @return void + */ + public function createIndex($index, $settings) + { + $this->getClient()->indices()->create([ + 'index' => $index, + 'body' => $settings, + ]); + } + + /** + * Delete an Elasticsearch index. + * + * @param string $index + * @return void + */ + public function deleteIndex($index) + { + $this->getClient()->indices()->delete(['index' => $index]); + } + + /** + * Check if index is empty. + * + * @param string $index + * @return bool + */ + public function isEmptyIndex($index) + { + $stats = $this->getClient()->indices()->stats(['index' => $index, 'metric' => 'docs']); + if ($stats['indices'][$index]['primaries']['docs']['count'] == 0) { + return true; + } + return false; + } + + /** + * Updates alias. + * + * @param string $alias + * @param string $newIndex + * @param string $oldIndex + * @return void + */ + public function updateAlias($alias, $newIndex, $oldIndex = '') + { + $params['body'] = ['actions' => []]; + if ($oldIndex) { + $params['body']['actions'][] = ['remove' => ['alias' => $alias, 'index' => $oldIndex]]; + } + if ($newIndex) { + $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $newIndex]]; + } + + $this->getClient()->indices()->updateAliases($params); + } + + /** + * Checks whether Elasticsearch index exists + * + * @param string $index + * @return bool + */ + public function indexExists($index) + { + return $this->getClient()->indices()->exists(['index' => $index]); + } + + /** + * Exists alias. + * + * @param string $alias + * @param string $index + * @return bool + */ + public function existsAlias($alias, $index = '') + { + $params = ['name' => $alias]; + if ($index) { + $params['index'] = $index; + } + return $this->getClient()->indices()->existsAlias($params); + } + + /** + * Get alias. + * + * @param string $alias + * @return array + */ + public function getAlias($alias) + { + return $this->getClient()->indices()->getAlias(['name' => $alias]); + } + + /** + * Add mapping to Elasticsearch index + * + * @param array $fields + * @param string $index + * @param string $entityType + * @return void + */ + public function addFieldsMapping(array $fields, $index, $entityType) + { + $params = [ + 'index' => $index, + 'type' => $entityType, + 'body' => [ + $entityType => [ + 'properties' => [ + '_search' => [ + 'type' => 'text' + ], + ], + 'dynamic_templates' => [ + [ + 'price_mapping' => [ + 'match' => 'price_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'float', + 'store' => true, + ], + ], + ], + [ + 'string_mapping' => [ + 'match' => '*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'text', + 'index' => false, + 'copy_to' => '_search' + ], + ], + ], + [ + 'position_mapping' => [ + 'match' => 'position_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'int', + ], + ], + ], + ], + ], + ], + ]; + + foreach ($fields as $field => $fieldInfo) { + $params['body'][$entityType]['properties'][$field] = $fieldInfo; + } + + $this->getClient()->indices()->putMapping($params); + } + + /** + * Delete mapping in Elasticsearch index + * + * @param string $index + * @param string $entityType + * @return void + */ + public function deleteMapping($index, $entityType) + { + $this->getClient()->indices()->deleteMapping([ + 'index' => $index, + 'type' => $entityType, + ]); + } + + /** + * Execute search by $query + * + * @param array $query + * @return array + */ + public function query($query) + { + return $this->getClient()->search($query); + } + + /** + * Execute suggest query + * + * @param array $query + * @return array + */ + public function suggest($query) + { + return $this->getClient()->suggest($query); + } +} diff --git a/app/code/Magento/Elasticsearch6/Model/Config.php b/app/code/Magento/Elasticsearch6/Model/Config.php new file mode 100644 index 0000000000000..aec00d92ce029 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/Model/Config.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Elasticsearch6\Model; + +use Magento\Framework\Search\EngineResolverInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\AdvancedSearch\Model\Client\ClientResolver; + +/** + * Elasticsearch6 config model + */ +class Config extends \Magento\Elasticsearch\Model\Config +{ + /** + * Search engine name + */ + private const ENGINE_NAME_6 = 'elasticsearch6'; + + /** + * @var EngineResolverInterface + */ + private $engineResolver; + + /** + * Constructor + * + * @param ScopeConfigInterface $scopeConfig + * @param ClientResolver|null $clientResolver + * @param EngineResolverInterface|null $engineResolver + * @param string|null $prefix + */ + public function __construct( + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\AdvancedSearch\Model\Client\ClientResolver $clientResolver = null, + \Magento\Framework\Search\EngineResolverInterface $engineResolver = null, + $prefix = null + ) { + parent::__construct($scopeConfig, $clientResolver, $engineResolver, $prefix); + $this->engineResolver = $engineResolver ?: ObjectManager::getInstance()->get(EngineResolverInterface::class); + } + + /** + * Return true if third party search engine is used + * + * @return bool + * @since 100.1.0 + */ + public function isElasticsearchEnabled() + { + return in_array($this->engineResolver->getCurrentSearchEngine(), [self::ENGINE_NAME_6]); + } +} diff --git a/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php b/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php new file mode 100644 index 0000000000000..24412fa6baec8 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php @@ -0,0 +1,271 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Elasticsearch6\Model\DataProvider; + +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Search\Model\QueryInterface; +use Magento\AdvancedSearch\Model\SuggestedQueriesInterface; +use Magento\Elasticsearch6\Model\Config; +use Magento\Elasticsearch\SearchAdapter\ConnectionManager; +use Magento\Search\Model\QueryResultFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; +use Magento\Store\Model\StoreManagerInterface as StoreManager; + +/** + * Class Suggestions + */ +class Suggestions implements SuggestedQueriesInterface +{ + /** + * @var Config + */ + private $config; + + /** + * @var QueryResultFactory + */ + private $queryResultFactory; + + /** + * @var ConnectionManager + */ + private $connectionManager; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var SearchIndexNameResolver + */ + private $searchIndexNameResolver; + + /** + * @var StoreManager + */ + private $storeManager; + + /** + * @var FieldProviderInterface + */ + private $fieldProvider; + + /** + * Suggestions constructor. + * + * @param ScopeConfigInterface $scopeConfig + * @param Config $config + * @param QueryResultFactory $queryResultFactory + * @param ConnectionManager $connectionManager + * @param SearchIndexNameResolver $searchIndexNameResolver + * @param StoreManager $storeManager + * @param FieldProviderInterface $fieldProvider + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + Config $config, + QueryResultFactory $queryResultFactory, + ConnectionManager $connectionManager, + SearchIndexNameResolver $searchIndexNameResolver, + StoreManager $storeManager, + FieldProviderInterface $fieldProvider + ) { + $this->queryResultFactory = $queryResultFactory; + $this->connectionManager = $connectionManager; + $this->scopeConfig = $scopeConfig; + $this->config = $config; + $this->searchIndexNameResolver = $searchIndexNameResolver; + $this->storeManager = $storeManager; + $this->fieldProvider = $fieldProvider; + } + + /** + * @inheritdoc + */ + public function getItems(QueryInterface $query) + { + $result = []; + if ($this->isSuggestionsAllowed()) { + $isResultsCountEnabled = $this->isResultsCountEnabled(); + + foreach ($this->getSuggestions($query) as $suggestion) { + $count = null; + if ($isResultsCountEnabled) { + $count = isset($suggestion['freq']) ? $suggestion['freq'] : null; + } + $result[] = $this->queryResultFactory->create( + [ + 'queryText' => $suggestion['text'], + 'resultsCount' => $count, + ] + ); + } + } + + return $result; + } + + /** + * @inheritdoc + */ + public function isResultsCountEnabled() + { + return $this->scopeConfig->isSetFlag( + SuggestedQueriesInterface::SEARCH_SUGGESTION_COUNT_RESULTS_ENABLED, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * Get Suggestions + * + * @param QueryInterface $query + * + * @return array + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function getSuggestions(QueryInterface $query) + { + $suggestions = []; + $searchSuggestionsCount = $this->getSearchSuggestionsCount(); + + $searchQuery = $this->initQuery($query); + $searchQuery = $this->addSuggestFields($searchQuery, $searchSuggestionsCount); + + $result = $this->fetchQuery($searchQuery); + + if (is_array($result)) { + foreach ($result['suggest'] ?? [] as $suggest) { + foreach ($suggest as $token) { + foreach ($token['options'] ?? [] as $key => $suggestion) { + $suggestions[$suggestion['score'] . '_' . $key] = $suggestion; + } + } + } + ksort($suggestions); + $texts = array_unique(array_column($suggestions, 'text')); + $suggestions = array_slice(array_intersect_key(array_values($suggestions), $texts), 0, $searchSuggestionsCount); + } + + return $suggestions; + } + + /** + * Init Search Query + * + * @param string $query + * + * @return array + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function initQuery($query) + { + $searchQuery = [ + 'index' => $this->searchIndexNameResolver->getIndexName( + $this->storeManager->getStore()->getId(), + Config::ELASTICSEARCH_TYPE_DEFAULT + ), + 'type' => Config::ELASTICSEARCH_TYPE_DEFAULT, + 'body' => [ + 'suggest' => [ + 'text' => $query->getQueryText() + ] + ], + ]; + + return $searchQuery; + } + + /** + * Build Suggest on searchable fields. + * + * @param array $searchQuery + * @param int $searchSuggestionsCount + * + * @return array + */ + private function addSuggestFields($searchQuery, $searchSuggestionsCount) + { + $fields = $this->getSuggestFields(); + foreach ($fields as $field) { + $searchQuery['body']['suggest']['phrase_' . $field] = [ + 'phrase' => [ + 'field' => $field, + 'analyzer' => 'standard', + 'size' => $searchSuggestionsCount, + 'max_errors' => 1, + 'direct_generator' => [ + [ + 'field' => $field, + 'min_word_length' => 3, + 'min_doc_freq' => 1, + ] + ], + ], + ]; + } + + return $searchQuery; + } + + /** + * Get fields to build suggest query on. + * + * @return array + */ + private function getSuggestFields() + { + $fields = array_filter($this->fieldProvider->getFields(), function ($field) { + return (($field['type'] ?? null) === 'text') && (($field['index'] ?? null) !== false); + }); + + return array_keys($fields); + } + + /** + * Fetch Query + * + * @param array $query + * @return array + */ + private function fetchQuery(array $query) + { + return $this->connectionManager->getConnection()->query($query); + } + + /** + * Get search suggestions Max Count from config + * + * @return int + */ + private function getSearchSuggestionsCount() + { + return (int) $this->scopeConfig->getValue( + SuggestedQueriesInterface::SEARCH_SUGGESTION_COUNT, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * Is Search Suggestions Allowed + * + * @return bool + */ + private function isSuggestionsAllowed() + { + $isSuggestionsEnabled = $this->scopeConfig->isSetFlag( + SuggestedQueriesInterface::SEARCH_SUGGESTION_ENABLED, + ScopeInterface::SCOPE_STORE + ); + $isEnabled = $this->config->isElasticsearchEnabled(); + $isSuggestionsAllowed = ($isEnabled && $isSuggestionsEnabled); + + return $isSuggestionsAllowed; + } +} diff --git a/app/code/Magento/Elasticsearch6/README.md b/app/code/Magento/Elasticsearch6/README.md new file mode 100644 index 0000000000000..8bf95ad95d147 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/README.md @@ -0,0 +1,2 @@ +Magento\Elasticsearch module allows to use Elastic search engine (v6) for product searching capabilities. +The module implements Magento\Search library interfaces. diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php new file mode 100644 index 0000000000000..c2a70d3f082f0 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php @@ -0,0 +1,118 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch6\Test\Unit\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver; + +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface + as FieldTypeResolver; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ConverterInterface + as FieldTypeConverterInterface; +use Magento\Elasticsearch6\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver; + +/** + * @SuppressWarnings(PHPMD) + */ +class DefaultResolverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var DefaultResolver + */ + private $resolver; + + /** + * @var FieldTypeResolver + */ + private $fieldTypeResolver; + + /** + * @var FieldTypeConverterInterface + */ + private $fieldTypeConverter; + + /** + * Set up test environment + * + * @return void + */ + protected function setUp() + { + $objectManager = new ObjectManagerHelper($this); + $this->fieldTypeResolver = $this->getMockBuilder(FieldTypeResolver::class) + ->disableOriginalConstructor() + ->setMethods(['getFieldType']) + ->getMockForAbstractClass(); + $this->fieldTypeConverter = $this->getMockBuilder(FieldTypeConverterInterface::class) + ->disableOriginalConstructor() + ->setMethods(['convert']) + ->getMockForAbstractClass(); + + $this->resolver = $objectManager->getObject( + DefaultResolver::class, + [ + 'fieldTypeResolver' => $this->fieldTypeResolver, + 'fieldTypeConverter' => $this->fieldTypeConverter + ] + ); + } + + /** + * @dataProvider getFieldNameProvider + * @param $fieldType + * @param $attributeCode + * @param $frontendInput + * @param $context + * @param $expected + * @return void + */ + public function testGetFieldName( + $fieldType, + $attributeCode, + $frontendInput, + $context, + $expected + ) { + $this->fieldTypeConverter->expects($this->any()) + ->method('convert') + ->willReturn('string'); + $attributeMock = $this->getMockBuilder(AttributeAdapter::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributeCode', 'getFrontendInput']) + ->getMock(); + $attributeMock->expects($this->any()) + ->method('getAttributeCode') + ->willReturn($attributeCode); + $attributeMock->expects($this->any()) + ->method('getFrontendInput') + ->willReturn($frontendInput); + $this->fieldTypeResolver->expects($this->any()) + ->method('getFieldType') + ->willReturn($fieldType); + + $this->assertEquals( + $expected, + $this->resolver->getFieldName($attributeMock, $context) + ); + } + + /** + * @return array + */ + public function getFieldNameProvider() + { + return [ + ['', 'code', '', [], 'code'], + ['', 'code', '', ['type' => 'default'], 'code'], + ['string', '*', '', ['type' => 'default'], '_search'], + ['', 'code', '', ['type' => 'default'], 'code'], + ['', 'code', 'select', ['type' => 'default'], 'code'], + ['', 'code', 'boolean', ['type' => 'default'], 'code'], + ['', 'code', '', ['type' => 'type'], 'sort_code'], + ]; + } +} diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php new file mode 100644 index 0000000000000..8276d0dd8dbe8 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php @@ -0,0 +1,562 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Elasticsearch6\Test\Unit\Model\Client; + +use Magento\Elasticsearch\Model\Client\Elasticsearch as ElasticsearchClient; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class ElasticsearchTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ElasticsearchClient + */ + protected $model; + + /** + * @var \Elasticsearch\Client|\PHPUnit_Framework_MockObject_MockObject + */ + protected $elasticsearchClientMock; + + /** + * @var \Elasticsearch\Namespaces\IndicesNamespace|\PHPUnit_Framework_MockObject_MockObject + */ + protected $indicesMock; + + /** + * @var ObjectManagerHelper + */ + protected $objectManager; + + /** + * Setup + * + * @return void + */ + protected function setUp() + { + $this->elasticsearchClientMock = $this->getMockBuilder(\Elasticsearch\Client::class) + ->setMethods([ + 'indices', + 'ping', + 'bulk', + 'search', + 'scroll', + 'suggest', + 'info', + ]) + ->disableOriginalConstructor() + ->getMock(); + $this->indicesMock = $this->getMockBuilder(\Elasticsearch\Namespaces\IndicesNamespace::class) + ->setMethods([ + 'exists', + 'getSettings', + 'create', + 'delete', + 'putMapping', + 'deleteMapping', + 'stats', + 'updateAliases', + 'existsAlias', + 'getAlias', + ]) + ->disableOriginalConstructor() + ->getMock(); + $this->elasticsearchClientMock->expects($this->any()) + ->method('indices') + ->willReturn($this->indicesMock); + $this->elasticsearchClientMock->expects($this->any()) + ->method('ping') + ->willReturn(true); + $this->elasticsearchClientMock->expects($this->any()) + ->method('info') + ->willReturn(['version' => ['number' => '6.0.0']]); + + $this->objectManager = new ObjectManagerHelper($this); + $this->model = $this->objectManager->getObject( + \Magento\Elasticsearch6\Model\Client\Elasticsearch::class, + [ + 'options' => $this->getOptions(), + 'elasticsearchClient' => $this->elasticsearchClientMock + ] + ); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testConstructorOptionsException() + { + $result = $this->objectManager->getObject( + \Magento\Elasticsearch6\Model\Client\Elasticsearch::class, + [ + 'options' => [] + ] + ); + $this->assertNotNull($result); + } + + /** + * Test client creation from the list of options + */ + public function testConstructorWithOptions() + { + $result = $this->objectManager->getObject( + \Magento\Elasticsearch6\Model\Client\Elasticsearch::class, + [ + 'options' => $this->getOptions() + ] + ); + $this->assertNotNull($result); + } + + /** + * Test ping functionality + */ + public function testPing() + { + $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(true); + $this->assertEquals(true, $this->model->ping()); + } + + /** + * Test validation of connection parameters + */ + public function testTestConnection() + { + $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(true); + $this->assertEquals(true, $this->model->testConnection()); + } + + /** + * Test validation of connection parameters returns false + */ + public function testTestConnectionFalse() + { + $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(false); + $this->assertEquals(true, $this->model->testConnection()); + } + + /** + * Test validation of connection parameters + */ + public function testTestConnectionPing() + { + $this->model = $this->objectManager->getObject( + \Magento\Elasticsearch6\Model\Client\Elasticsearch::class, + [ + 'options' => $this->getEmptyIndexOption(), + 'elasticsearchClient' => $this->elasticsearchClientMock + ] + ); + + $this->model->ping(); + $this->assertEquals(true, $this->model->testConnection()); + } + + /** + * Test bulkQuery() method + */ + public function testBulkQuery() + { + $this->elasticsearchClientMock->expects($this->once()) + ->method('bulk') + ->with([]); + $this->model->bulkQuery([]); + } + + /** + * Test createIndex() method, case when such index exists + */ + public function testCreateIndexExists() + { + $this->indicesMock->expects($this->once()) + ->method('create') + ->with([ + 'index' => 'indexName', + 'body' => [], + ]); + $this->model->createIndex('indexName', []); + } + + /** + * Test deleteIndex() method. + */ + public function testDeleteIndex() + { + $this->indicesMock->expects($this->once()) + ->method('delete') + ->with(['index' => 'indexName']); + $this->model->deleteIndex('indexName'); + } + + /** + * Test isEmptyIndex() method. + */ + public function testIsEmptyIndex() + { + $indexName = 'magento2_index'; + $stats['indices'][$indexName]['primaries']['docs']['count'] = 0; + + $this->indicesMock->expects($this->once()) + ->method('stats') + ->with(['index' => $indexName, 'metric' => 'docs']) + ->willReturn($stats); + $this->assertTrue($this->model->isEmptyIndex($indexName)); + } + + /** + * Test isEmptyIndex() method returns false. + */ + public function testIsEmptyIndexFalse() + { + $indexName = 'magento2_index'; + $stats['indices'][$indexName]['primaries']['docs']['count'] = 1; + + $this->indicesMock->expects($this->once()) + ->method('stats') + ->with(['index' => $indexName, 'metric' => 'docs']) + ->willReturn($stats); + $this->assertFalse($this->model->isEmptyIndex($indexName)); + } + + /** + * Test updateAlias() method with new index. + */ + public function testUpdateAlias() + { + $alias = 'alias1'; + $index = 'index1'; + + $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $index]]; + + $this->indicesMock->expects($this->once()) + ->method('updateAliases') + ->with($params); + $this->model->updateAlias($alias, $index); + } + + /** + * Test updateAlias() method with new and old index. + */ + public function testUpdateAliasRemoveOldIndex() + { + $alias = 'alias1'; + $newIndex = 'index1'; + $oldIndex = 'indexOld'; + + $params['body']['actions'][] = ['remove' => ['alias' => $alias, 'index' => $oldIndex]]; + $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $newIndex]]; + + $this->indicesMock->expects($this->once()) + ->method('updateAliases') + ->with($params); + $this->model->updateAlias($alias, $newIndex, $oldIndex); + } + + /** + * Test indexExists() method, case when no such index exists + */ + public function testIndexExists() + { + $this->indicesMock->expects($this->once()) + ->method('exists') + ->with([ + 'index' => 'indexName', + ]) + ->willReturn(true); + $this->model->indexExists('indexName'); + } + + /** + * Tests existsAlias() method checking for alias. + */ + public function testExistsAlias() + { + $alias = 'alias1'; + $params = ['name' => $alias]; + $this->indicesMock->expects($this->once()) + ->method('existsAlias') + ->with($params) + ->willReturn(true); + $this->assertTrue($this->model->existsAlias($alias)); + } + + /** + * Tests existsAlias() method checking for alias and index. + */ + public function testExistsAliasWithIndex() + { + $alias = 'alias1'; + $index = 'index1'; + $params = ['name' => $alias, 'index' => $index]; + $this->indicesMock->expects($this->once()) + ->method('existsAlias') + ->with($params) + ->willReturn(true); + $this->assertTrue($this->model->existsAlias($alias, $index)); + } + + /** + * Test getAlias() method. + */ + public function testGetAlias() + { + $alias = 'alias1'; + $params = ['name' => $alias]; + $this->indicesMock->expects($this->once()) + ->method('getAlias') + ->with($params) + ->willReturn([]); + $this->assertEquals([], $this->model->getAlias($alias)); + } + + /** + * Test createIndexIfNotExists() method, case when operation fails + * @expectedException \Exception + */ + public function testCreateIndexFailure() + { + $this->indicesMock->expects($this->once()) + ->method('create') + ->with([ + 'index' => 'indexName', + 'body' => [], + ]) + ->willThrowException(new \Exception('Something went wrong')); + $this->model->createIndex('indexName', []); + } + + /** + * Test testAddFieldsMapping() method + */ + public function testAddFieldsMapping() + { + $this->indicesMock->expects($this->once()) + ->method('putMapping') + ->with([ + 'index' => 'indexName', + 'type' => 'product', + 'body' => [ + 'product' => [ + 'properties' => [ + '_search' => [ + 'type' => 'text', + ], + 'name' => [ + 'type' => 'text', + ], + ], + 'dynamic_templates' => [ + [ + 'price_mapping' => [ + 'match' => 'price_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'float', + 'store' => true, + ], + ], + ], + [ + 'string_mapping' => [ + 'match' => '*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'text', + 'index' => false, + 'copy_to' => '_search' + ], + ], + ], + [ + 'position_mapping' => [ + 'match' => 'position_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'int', + ], + ], + ], + ], + ], + ], + ]); + $this->model->addFieldsMapping( + [ + 'name' => [ + 'type' => 'text', + ], + ], + 'indexName', + 'product' + ); + } + + /** + * Test testAddFieldsMapping() method + * @expectedException \Exception + */ + public function testAddFieldsMappingFailure() + { + $this->indicesMock->expects($this->once()) + ->method('putMapping') + ->with([ + 'index' => 'indexName', + 'type' => 'product', + 'body' => [ + 'product' => [ + 'properties' => [ + '_search' => [ + 'type' => 'text', + ], + 'name' => [ + 'type' => 'text', + ], + ], + 'dynamic_templates' => [ + [ + 'price_mapping' => [ + 'match' => 'price_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'float', + 'store' => true, + ], + ], + ], + [ + 'string_mapping' => [ + 'match' => '*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'text', + 'index' => false, + 'copy_to' => '_search' + ], + ], + ], + [ + 'position_mapping' => [ + 'match' => 'position_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'int', + ], + ], + ], + ], + ], + ], + ]) + ->willThrowException(new \Exception('Something went wrong')); + $this->model->addFieldsMapping( + [ + 'name' => [ + 'type' => 'text', + ], + ], + 'indexName', + 'product' + ); + } + + /** + * Test deleteMapping() method + */ + public function testDeleteMapping() + { + $this->indicesMock->expects($this->once()) + ->method('deleteMapping') + ->with([ + 'index' => 'indexName', + 'type' => 'product', + ]); + $this->model->deleteMapping( + 'indexName', + 'product' + ); + } + + /** + * Test deleteMapping() method + * @expectedException \Exception + */ + public function testDeleteMappingFailure() + { + $this->indicesMock->expects($this->once()) + ->method('deleteMapping') + ->with([ + 'index' => 'indexName', + 'type' => 'product', + ]) + ->willThrowException(new \Exception('Something went wrong')); + $this->model->deleteMapping( + 'indexName', + 'product' + ); + } + + /** + * Test query() method + * @return void + */ + public function testQuery() + { + $query = 'test phrase query'; + $this->elasticsearchClientMock->expects($this->once()) + ->method('search') + ->with($query) + ->willReturn([]); + $this->assertEquals([], $this->model->query($query)); + } + + /** + * Test suggest() method + * @return void + */ + public function testSuggest() + { + $query = 'query'; + $this->elasticsearchClientMock->expects($this->once()) + ->method('suggest') + ->willReturn([]); + $this->assertEquals([], $this->model->suggest($query)); + } + + /** + * Get elasticsearch client options + * + * @return array + */ + protected function getOptions() + { + return [ + 'hostname' => 'localhost', + 'port' => '9200', + 'timeout' => 15, + 'index' => 'magento2', + 'enableAuth' => 1, + 'username' => 'user', + 'password' => 'passwd', + ]; + } + + /** + * @return array + */ + protected function getEmptyIndexOption() + { + return [ + 'hostname' => 'localhost', + 'port' => '9200', + 'index' => '', + 'timeout' => 15, + 'enableAuth' => 1, + 'username' => 'user', + 'password' => 'passwd', + ]; + } +} diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/DataProvider/SuggestionsTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/DataProvider/SuggestionsTest.php new file mode 100644 index 0000000000000..957edc559fdcb --- /dev/null +++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/DataProvider/SuggestionsTest.php @@ -0,0 +1,183 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Elasticsearch6\Test\Unit\Model\DataProvider; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Elasticsearch\Model\DataProvider\Suggestions; +use Magento\Elasticsearch\Model\Config; +use Magento\Elasticsearch\SearchAdapter\ConnectionManager; +use Magento\Search\Model\QueryResultFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; +use Magento\Store\Model\StoreManagerInterface as StoreManager; +use Magento\Search\Model\QueryInterface; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class SuggestionsTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Suggestions + */ + private $model; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $config; + + /** + * @var QueryResultFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $queryResultFactory; + + /** + * @var ConnectionManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionManager; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfig; + + /** + * @var SearchIndexNameResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $searchIndexNameResolver; + + /** + * @var StoreManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManager; + + /** + * @var QueryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $query; + + /** + * Set up test environment + * + * @return void + */ + protected function setUp() + { + $this->config = $this->getMockBuilder(\Magento\Elasticsearch6\Model\Config::class) + ->disableOriginalConstructor() + ->setMethods(['isElasticsearchEnabled']) + ->getMock(); + + $this->queryResultFactory = $this->getMockBuilder(\Magento\Search\Model\QueryResultFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->connectionManager = $this->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\ConnectionManager::class) + ->disableOriginalConstructor() + ->setMethods(['getConnection']) + ->getMock(); + + $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->searchIndexNameResolver = $this + ->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver::class) + ->disableOriginalConstructor() + ->setMethods(['getIndexName']) + ->getMock(); + + $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->query = $this->getMockBuilder(\Magento\Search\Model\QueryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new ObjectManagerHelper($this); + + $this->model = $objectManager->getObject( + \Magento\Elasticsearch6\Model\DataProvider\Suggestions::class, + [ + 'queryResultFactory' => $this->queryResultFactory, + 'connectionManager' => $this->connectionManager, + 'scopeConfig' => $this->scopeConfig, + 'config' => $this->config, + 'searchIndexNameResolver' => $this->searchIndexNameResolver, + 'storeManager' => $this->storeManager + ] + ); + } + + /** + * Test getItems() method + */ + public function testGetItems() + { + $this->scopeConfig->expects($this->any()) + ->method('getValue') + ->willReturn(1); + + $this->config->expects($this->any()) + ->method('isElasticsearchEnabled') + ->willReturn(1); + + $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->storeManager->expects($this->any()) + ->method('getStore') + ->willReturn($store); + + $store->expects($this->any()) + ->method('getId') + ->willReturn(1); + + $this->searchIndexNameResolver->expects($this->any()) + ->method('getIndexName') + ->willReturn('magento2_product_1'); + + $this->query->expects($this->any()) + ->method('getQueryText') + ->willReturn('query'); + + $client = $this->getMockBuilder(\Magento\Elasticsearch6\Model\Client\Elasticsearch::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionManager->expects($this->any()) + ->method('getConnection') + ->willReturn($client); + + $client->expects($this->any()) + ->method('query') + ->willReturn([ + 'suggest' => [ + 'phrase_field' => [ + 'options' => [ + 'text' => 'query', + 'score' => 1, + 'freq' => 1, + ] + ], + ], + ]); + + $query = $this->getMockBuilder(\Magento\Search\Model\QueryResult::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->queryResultFactory->expects($this->any()) + ->method('create') + ->willReturn($query); + + $this->assertInternalType('array', $this->model->getItems($this->query)); + } +} diff --git a/app/code/Magento/Elasticsearch6/composer.json b/app/code/Magento/Elasticsearch6/composer.json new file mode 100644 index 0000000000000..ec9fc3b463f5d --- /dev/null +++ b/app/code/Magento/Elasticsearch6/composer.json @@ -0,0 +1,26 @@ +{ + "name": "magento/module-elasticsearch-6", + "description": "N/A", + "require": { + "php": "~7.1.3||~7.2.0", + "magento/framework": "*", + "magento/module-elasticsearch": ">=100.3.0", + "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1" + }, + "suggest": { + "magento/module-config": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\Elasticsearch6\\": "" + } + } +} diff --git a/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml b/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..067a0acb8c908 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="catalog"> + <group id="search"> + <!-- Elasticsearch 6.0+ --> + <field id="elasticsearch6_server_hostname" translate="label" type="text" sortOrder="71" + showInDefault="1" showInWebsite="0" showInStore="0"> + <label>Elasticsearch Server Hostname</label> + <depends> + <field id="engine">elasticsearch6</field> + </depends> + </field> + + <field id="elasticsearch6_server_port" translate="label" type="text" sortOrder="72" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Elasticsearch Server Port</label> + <depends> + <field id="engine">elasticsearch6</field> + </depends> + </field> + + <field id="elasticsearch6_index_prefix" translate="label" type="text" sortOrder="73" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Elasticsearch Index Prefix</label> + <depends> + <field id="engine">elasticsearch6</field> + </depends> + </field> + + <field id="elasticsearch6_enable_auth" translate="label" type="select" sortOrder="74" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Enable Elasticsearch HTTP Auth</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <depends> + <field id="engine">elasticsearch6</field> + </depends> + </field> + + <field id="elasticsearch6_username" translate="label" type="text" sortOrder="75" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Elasticsearch HTTP Username</label> + <depends> + <field id="engine">elasticsearch6</field> + <field id="elasticsearch6_enable_auth">1</field> + </depends> + </field> + + <field id="elasticsearch6_password" translate="label" type="text" sortOrder="76" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Elasticsearch HTTP Password</label> + <depends> + <field id="engine">elasticsearch6</field> + <field id="elasticsearch6_enable_auth">1</field> + </depends> + </field> + + <field id="elasticsearch6_server_timeout" translate="label" type="text" sortOrder="77" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Elasticsearch Server Timeout</label> + <depends> + <field id="engine">elasticsearch6</field> + </depends> + </field> + + <field id="elasticsearch6_test_connect_wizard" translate="button_label" sortOrder="78" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label/> + <button_label>Test Connection</button_label> + <frontend_model>Magento\Elasticsearch6\Block\Adminhtml\System\Config\TestConnection</frontend_model> + <depends> + <field id="engine">elasticsearch6</field> + </depends> + </field> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/Elasticsearch6/etc/config.xml b/app/code/Magento/Elasticsearch6/etc/config.xml new file mode 100644 index 0000000000000..047ae977fdef1 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/etc/config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> + <default> + <catalog> + <search> + <elasticsearch6_server_hostname>localhost</elasticsearch6_server_hostname> + <elasticsearch6_server_port>9200</elasticsearch6_server_port> + <elasticsearch6_index_prefix>magento2</elasticsearch6_index_prefix> + <elasticsearch6_enable_auth>0</elasticsearch6_enable_auth> + <elasticsearch6_server_timeout>15</elasticsearch6_server_timeout> + </search> + </catalog> + </default> +</config> diff --git a/app/code/Magento/Elasticsearch6/etc/di.xml b/app/code/Magento/Elasticsearch6/etc/di.xml new file mode 100644 index 0000000000000..25eff42fd3442 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/etc/di.xml @@ -0,0 +1,165 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Search\Model\Adminhtml\System\Config\Source\Engine"> + <arguments> + <argument name="engines" xsi:type="array"> + <item name="elasticsearch6" xsi:type="string">Elasticsearch 6.0+</item> + </argument> + </arguments> + </type> + + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper\CategoryFieldsProviderProxy"> + <arguments> + <argument name="categoryFieldsProviders" xsi:type="array"> + <item name="elasticsearch6" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper\CategoryFieldsProvider</item> + </argument> + </arguments> + </type> + + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapperProxy"> + <arguments> + <argument name="dataMappers" xsi:type="array"> + <item name="elasticsearch6" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapper</item> + </argument> + </arguments> + </type> + + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapperProxy"> + <arguments> + <argument name="productFieldMappers" xsi:type="array"> + <item name="elasticsearch6" xsi:type="object">Magento\Elasticsearch6\Model\Adapter\FieldMapper\ProductFieldMapper</item> + </argument> + </arguments> + </type> + + <type name="Magento\AdvancedSearch\Model\Client\ClientResolver"> + <arguments> + <argument name="clientFactories" xsi:type="array"> + <item name="elasticsearch6" xsi:type="string">\Magento\Elasticsearch6\Model\Client\ElasticsearchFactory</item> + </argument> + <argument name="clientOptions" xsi:type="array"> + <item name="elasticsearch6" xsi:type="string">\Magento\Elasticsearch6\Model\Config</item> + </argument> + </arguments> + </type> + + <type name="Magento\CatalogSearch\Model\Indexer\IndexerHandlerFactory"> + <arguments> + <argument name="handlers" xsi:type="array"> + <item name="elasticsearch6" xsi:type="string">Magento\Elasticsearch\Model\Indexer\IndexerHandler</item> + </argument> + </arguments> + </type> + + <type name="Magento\CatalogSearch\Model\Indexer\IndexStructureFactory"> + <arguments> + <argument name="structures" xsi:type="array"> + <item name="elasticsearch6" xsi:type="string">Magento\Elasticsearch\Model\Indexer\IndexStructure</item> + </argument> + </arguments> + </type> + + <type name="Magento\CatalogSearch\Model\ResourceModel\EngineProvider"> + <arguments> + <argument name="engines" xsi:type="array"> + <item name="elasticsearch6" xsi:type="string">Magento\Elasticsearch\Model\ResourceModel\Engine</item> + </argument> + </arguments> + </type> + + <type name="Magento\Search\Model\AdapterFactory"> + <arguments> + <argument name="adapters" xsi:type="array"> + <item name="elasticsearch6" xsi:type="string">Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Adapter</item> + </argument> + </arguments> + </type> + + <type name="Magento\Search\Model\EngineResolver"> + <arguments> + <argument name="engines" xsi:type="array"> + <item name="elasticsearch6" xsi:type="string">elasticsearch6</item> + </argument> + </arguments> + </type> + + <virtualType name="Magento\Elasticsearch6\Model\Client\ElasticsearchFactory" type="Magento\AdvancedSearch\Model\Client\ClientFactory"> + <arguments> + <argument name="clientClass" xsi:type="string">Magento\Elasticsearch6\Model\Client\Elasticsearch</argument> + </arguments> + </virtualType> + + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Client\ClientFactoryProxy"> + <arguments> + <argument name="clientFactories" xsi:type="array"> + <item name="elasticsearch6" xsi:type="object">Magento\Elasticsearch6\Model\Client\ElasticsearchFactory</item> + </argument> + </arguments> + </type> + + <type name="Magento\Framework\Search\Dynamic\IntervalFactory"> + <arguments> + <argument name="intervals" xsi:type="array"> + <item name="elasticsearch6" xsi:type="string">Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Aggregation\Interval</item> + </argument> + </arguments> + </type> + + <type name="Magento\Framework\Search\Dynamic\DataProviderFactory"> + <arguments> + <argument name="dataProviders" xsi:type="array"> + <item name="elasticsearch6" xsi:type="string">Magento\Elasticsearch\SearchAdapter\Dynamic\DataProvider</item> + </argument> + </arguments> + </type> + + + <type name="Magento\AdvancedSearch\Model\SuggestedQueries"> + <arguments> + <argument name="data" xsi:type="array"> + <item name="elasticsearch6" xsi:type="string">Magento\Elasticsearch6\Model\DataProvider\Suggestions</item> + </argument> + </arguments> + </type> + + <type name="Magento\Elasticsearch6\Model\DataProvider\Suggestions"> + <arguments> + <argument name="fieldProvider" xsi:type="object">elasticsearch5FieldProvider</argument> + </arguments> + </type> + + <virtualType name="elasticsearch6FieldNameResolver" type="\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver"> + <arguments> + <argument name="items" xsi:type="array"> + <item name="notEav" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\NotEavAttribute</item> + <item name="special" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\SpecialAttribute</item> + <item name="price" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\Price</item> + <item name="categoryName" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CategoryName</item> + <item name="position" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\Position</item> + <item name="default" xsi:type="object">\Magento\Elasticsearch6\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver</item> + </argument> + </arguments> + </virtualType> + + <virtualType name="Magento\Elasticsearch6\Model\Adapter\FieldMapper\ProductFieldMapper" + type="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapper"> + <arguments> + <argument name="fieldProvider" xsi:type="object">elasticsearch5FieldProvider</argument> + <argument name="fieldNameResolver" xsi:type="object">elasticsearch6FieldNameResolver</argument> + </arguments> + </virtualType> + + <type name="Magento\Search\Model\Search\PageSizeProvider"> + <arguments> + <argument name="pageSizeBySearchEngine" xsi:type="array"> + <item name="elasticsearch6" xsi:type="number">2147483647</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/Elasticsearch6/etc/module.xml b/app/code/Magento/Elasticsearch6/etc/module.xml new file mode 100644 index 0000000000000..8756793621aa8 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/etc/module.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_Elasticsearch6"> + <sequence> + <module name="Magento_CatalogSearch"/> + <module name="Magento_Search"/> + <module name="Magento_AdvancedSearch"/> + <module name="Magento_Elasticsearch"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/Elasticsearch6/registration.php b/app/code/Magento/Elasticsearch6/registration.php new file mode 100644 index 0000000000000..7ab10e996eb8c --- /dev/null +++ b/app/code/Magento/Elasticsearch6/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_Elasticsearch6', + __DIR__ +); From 306255b2009757546c859fc3eab3de6a8245ff28 Mon Sep 17 00:00:00 2001 From: Romain Ruaud <romain.ruaud@smile.fr> Date: Tue, 26 Feb 2019 12:28:56 +0100 Subject: [PATCH 083/108] Allow more recent version of ES client. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7120fe77b0e55..28ffb92218752 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "colinmollenhour/credis": "1.10.0", "colinmollenhour/php-redis-session-abstract": "~1.4.0", "composer/composer": "^1.6", - "elasticsearch/elasticsearch": "~2.0|~5.1", + "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1", "magento/composer": "~1.4.0", "magento/magento-composer-installer": ">=0.1.11", "magento/zendframework1": "~1.14.1", From 45458e65c51f4457d8764abd898ac81452c34fea Mon Sep 17 00:00:00 2001 From: Romain Ruaud <romain.ruaud@smile.fr> Date: Tue, 26 Feb 2019 18:16:36 +0100 Subject: [PATCH 084/108] Update composer.json replace. --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 28ffb92218752..90954101d8d40 100644 --- a/composer.json +++ b/composer.json @@ -149,6 +149,7 @@ "magento/module-downloadable-import-export": "*", "magento/module-eav": "*", "magento/module-elasticsearch": "*", + "magento/module-elasticsearch-6": "*", "magento/module-email": "*", "magento/module-encryption-key": "*", "magento/module-fedex": "*", From 9734c2fbd903576c5f05f4ae734505db3f9139cd Mon Sep 17 00:00:00 2001 From: Romain Ruaud <romain.ruaud@smile.fr> Date: Wed, 27 Feb 2019 15:57:54 +0100 Subject: [PATCH 085/108] Update module dependencies. --- app/code/Magento/Elasticsearch6/composer.json | 4 ++++ app/code/Magento/Elasticsearch6/etc/module.xml | 1 + 2 files changed, 5 insertions(+) diff --git a/app/code/Magento/Elasticsearch6/composer.json b/app/code/Magento/Elasticsearch6/composer.json index ec9fc3b463f5d..6288aff8490bb 100644 --- a/app/code/Magento/Elasticsearch6/composer.json +++ b/app/code/Magento/Elasticsearch6/composer.json @@ -4,6 +4,10 @@ "require": { "php": "~7.1.3||~7.2.0", "magento/framework": "*", + "magento/module-advanced-search": "*", + "magento/module-catalog-search": "*", + "magento/module-search": "*", + "magento/module-store": "*", "magento/module-elasticsearch": ">=100.3.0", "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1" }, diff --git a/app/code/Magento/Elasticsearch6/etc/module.xml b/app/code/Magento/Elasticsearch6/etc/module.xml index 8756793621aa8..4fde2394dfbdd 100644 --- a/app/code/Magento/Elasticsearch6/etc/module.xml +++ b/app/code/Magento/Elasticsearch6/etc/module.xml @@ -11,6 +11,7 @@ <module name="Magento_CatalogSearch"/> <module name="Magento_Search"/> <module name="Magento_AdvancedSearch"/> + <module name="Magento_Store"/> <module name="Magento_Elasticsearch"/> </sequence> </module> From ae22130b706ad2e1224583e5303c17af6228b145 Mon Sep 17 00:00:00 2001 From: Romain Ruaud <romain.ruaud@smile.fr> Date: Wed, 27 Feb 2019 16:00:06 +0100 Subject: [PATCH 086/108] Remove excessive since. --- app/code/Magento/Elasticsearch6/Model/Config.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/Elasticsearch6/Model/Config.php b/app/code/Magento/Elasticsearch6/Model/Config.php index aec00d92ce029..f21101f001d33 100644 --- a/app/code/Magento/Elasticsearch6/Model/Config.php +++ b/app/code/Magento/Elasticsearch6/Model/Config.php @@ -48,7 +48,6 @@ public function __construct( * Return true if third party search engine is used * * @return bool - * @since 100.1.0 */ public function isElasticsearchEnabled() { From c73a128a24041f038b13a683133768c6b42764e3 Mon Sep 17 00:00:00 2001 From: Romain Ruaud <romain.ruaud@smile.fr> Date: Wed, 27 Feb 2019 16:02:29 +0100 Subject: [PATCH 087/108] Set dependencies as required. --- app/code/Magento/Elasticsearch6/Model/Config.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Elasticsearch6/Model/Config.php b/app/code/Magento/Elasticsearch6/Model/Config.php index f21101f001d33..1a989e2705fdd 100644 --- a/app/code/Magento/Elasticsearch6/Model/Config.php +++ b/app/code/Magento/Elasticsearch6/Model/Config.php @@ -36,12 +36,12 @@ class Config extends \Magento\Elasticsearch\Model\Config */ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\AdvancedSearch\Model\Client\ClientResolver $clientResolver = null, - \Magento\Framework\Search\EngineResolverInterface $engineResolver = null, + \Magento\AdvancedSearch\Model\Client\ClientResolver $clientResolver, + \Magento\Framework\Search\EngineResolverInterface $engineResolver, $prefix = null ) { parent::__construct($scopeConfig, $clientResolver, $engineResolver, $prefix); - $this->engineResolver = $engineResolver ?: ObjectManager::getInstance()->get(EngineResolverInterface::class); + $this->engineResolver = $engineResolver; } /** From a9c865a25074452588b99c388afaa0a8e852d87c Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Thu, 28 Feb 2019 17:09:09 +0000 Subject: [PATCH 088/108] magento/magento2#21458: Fixed static tests --- .../Block/Adminhtml/System/Config/TestConnection.php | 2 +- .../FieldProvider/FieldName/Resolver/DefaultResolver.php | 3 ++- .../Elasticsearch6/Model/DataProvider/Suggestions.php | 8 ++++++-- .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php b/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php index 5573d959fa372..8f99ef14dc646 100644 --- a/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php +++ b/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php @@ -12,7 +12,7 @@ class TestConnection extends \Magento\AdvancedSearch\Block\Adminhtml\System\Config\TestConnection { /** - * {@inheritdoc} + * @inheritdoc */ protected function _getFieldMapping() { diff --git a/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php b/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php index 2420335a95dc5..7532927f1dc85 100644 --- a/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php +++ b/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php @@ -8,11 +8,12 @@ namespace Magento\Elasticsearch6\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver as Base; /** * Default name resolver. */ -class DefaultResolver extends \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver +class DefaultResolver extends Base { /** * Get field name. diff --git a/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php b/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php index 24412fa6baec8..77e1270f54fc2 100644 --- a/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php +++ b/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php @@ -150,7 +150,11 @@ private function getSuggestions(QueryInterface $query) } ksort($suggestions); $texts = array_unique(array_column($suggestions, 'text')); - $suggestions = array_slice(array_intersect_key(array_values($suggestions), $texts), 0, $searchSuggestionsCount); + $suggestions = array_slice( + array_intersect_key(array_values($suggestions), $texts), + 0, + $searchSuggestionsCount + ); } return $suggestions; @@ -186,7 +190,7 @@ private function initQuery($query) * Build Suggest on searchable fields. * * @param array $searchQuery - * @param int $searchSuggestionsCount + * @param int $searchSuggestionsCount * * @return array */ diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt index 837fef7a1935d..d85b8ba1fa225 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt @@ -208,3 +208,4 @@ Magento/InventoryGroupedProductIndexer/Indexer Magento/Customer/Model/FileUploaderDataResolver.php Magento/Customer/Model/Customer/DataProvider.php Magento/InventoryShippingAdminUi/Ui/DataProvider +Magento/Elasticsearch/Model/Client/Elasticsearch.php From b4e41fe7d16b4e023edc1a117ec719053df42269 Mon Sep 17 00:00:00 2001 From: Ievgen Shakhsuvarov <ishakhsuvarov@magento.com> Date: Thu, 28 Feb 2019 21:45:37 -0600 Subject: [PATCH 089/108] magento/magento2#21458: Elasticsearch 6.x support - Updated package dependencis versions --- app/code/Magento/Elasticsearch6/composer.json | 4 ++-- .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Elasticsearch6/composer.json b/app/code/Magento/Elasticsearch6/composer.json index 6288aff8490bb..c289d8cd3e4e4 100644 --- a/app/code/Magento/Elasticsearch6/composer.json +++ b/app/code/Magento/Elasticsearch6/composer.json @@ -8,8 +8,8 @@ "magento/module-catalog-search": "*", "magento/module-search": "*", "magento/module-store": "*", - "magento/module-elasticsearch": ">=100.3.0", - "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1" + "magento/module-elasticsearch": "*", + "elasticsearch/elasticsearch": "~6.1" }, "suggest": { "magento/module-config": "*" diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt index d85b8ba1fa225..13fee19dd5924 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt @@ -208,4 +208,4 @@ Magento/InventoryGroupedProductIndexer/Indexer Magento/Customer/Model/FileUploaderDataResolver.php Magento/Customer/Model/Customer/DataProvider.php Magento/InventoryShippingAdminUi/Ui/DataProvider -Magento/Elasticsearch/Model/Client/Elasticsearch.php +Magento/Elasticsearch6/Model/Client/Elasticsearch.php From d83b16ac796822c5e65c85759730cc8989e92469 Mon Sep 17 00:00:00 2001 From: Ievgen Shakhsuvarov <ishakhsuvarov@magento.com> Date: Fri, 1 Mar 2019 11:02:25 -0600 Subject: [PATCH 090/108] magento/magento2#21458: Elasticsearch 6.x support - minor code quality improvements --- .../Block/Adminhtml/System/Config/TestConnection.php | 3 +-- .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php b/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php index 8f99ef14dc646..1b17db1a00f6e 100644 --- a/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php +++ b/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php @@ -6,8 +6,7 @@ namespace Magento\Elasticsearch6\Block\Adminhtml\System\Config; /** - * Elasticsearch 6x test connection block - * @codeCoverageIgnore + * Elasticsearch 6.x test connection block */ class TestConnection extends \Magento\AdvancedSearch\Block\Adminhtml\System\Config\TestConnection { diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt index 13fee19dd5924..0450ae1330cd8 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt @@ -208,4 +208,4 @@ Magento/InventoryGroupedProductIndexer/Indexer Magento/Customer/Model/FileUploaderDataResolver.php Magento/Customer/Model/Customer/DataProvider.php Magento/InventoryShippingAdminUi/Ui/DataProvider -Magento/Elasticsearch6/Model/Client/Elasticsearch.php +Magento/Elasticsearch6/Model/Client From 96268150bc9c2b065d37f4c8576f94e4d45723cb Mon Sep 17 00:00:00 2001 From: Ievgen Shakhsuvarov <ishakhsuvarov@magento.com> Date: Fri, 1 Mar 2019 11:04:44 -0600 Subject: [PATCH 091/108] magento/magento2#21458: Elasticsearch 6.x support - updated composer dependencies --- composer.lock | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/composer.lock b/composer.lock index 48aef8b603d4a..a880a6dea16d1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f22c780b1ed27ba951c0562e20078a70", + "content-hash": "19b030406adc1028eb58a37e975c5f74", "packages": [ { "name": "braintree/braintree_php", @@ -535,29 +535,31 @@ }, { "name": "elasticsearch/elasticsearch", - "version": "v5.4.0", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/elastic/elasticsearch-php.git", - "reference": "d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59" + "reference": "b237a37b2cdf23a5a17fd3576cdea771394ad00d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59", - "reference": "d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/b237a37b2cdf23a5a17fd3576cdea771394ad00d", + "reference": "b237a37b2cdf23a5a17fd3576cdea771394ad00d", "shasum": "" }, "require": { + "ext-json": ">=1.3.7", "guzzlehttp/ringphp": "~1.0", - "php": "^5.6|^7.0", + "php": "^7.0", "psr/log": "~1.0" }, "require-dev": { "cpliakas/git-wrapper": "~1.0", "doctrine/inflector": "^1.1", "mockery/mockery": "0.9.4", - "phpunit/phpunit": "^4.7|^5.4", - "sami/sami": "~3.2", + "phpstan/phpstan-shim": "0.8.3", + "phpunit/phpunit": "6.3.0", + "squizlabs/php_codesniffer": "3.0.2", "symfony/finder": "^2.8", "symfony/yaml": "^2.8" }, @@ -586,7 +588,7 @@ "elasticsearch", "search" ], - "time": "2019-01-08T18:57:00+00:00" + "time": "2019-01-08T18:53:46+00:00" }, { "name": "guzzlehttp/ringphp", @@ -7813,6 +7815,7 @@ "mock", "xunit" ], + "abandoned": true, "time": "2018-08-09T05:50:03+00:00" }, { From fa890bed53c33eecf182b90c68605af517f55dd9 Mon Sep 17 00:00:00 2001 From: Stepan Furman <furman.stepan@gmail.com> Date: Wed, 27 Feb 2019 22:45:35 +0200 Subject: [PATCH 092/108] MSI-1988 issue fixed: Database Rollback not working M2.3 --- app/code/Magento/Backup/Model/Db.php | 24 +++- .../ResourceModel/Table/GetListTables.php | 42 +++++++ .../Model/ResourceModel/View/GetListViews.php | 41 +++++++ .../ResourceModel/View/GetViewsBackup.php | 106 ++++++++++++++++++ 4 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php create mode 100644 app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php create mode 100644 app/code/Magento/Backup/Model/ResourceModel/View/GetViewsBackup.php diff --git a/app/code/Magento/Backup/Model/Db.php b/app/code/Magento/Backup/Model/Db.php index bc458a0a8e4bf..a385a476845db 100644 --- a/app/code/Magento/Backup/Model/Db.php +++ b/app/code/Magento/Backup/Model/Db.php @@ -6,6 +6,8 @@ namespace Magento\Backup\Model; use Magento\Backup\Helper\Data as Helper; +use Magento\Backup\Model\ResourceModel\Table\GetListTables; +use Magento\Backup\Model\ResourceModel\View\GetViewsBackup; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\RuntimeException; @@ -43,19 +45,35 @@ class Db implements \Magento\Framework\Backup\Db\BackupDbInterface */ private $helper; + /** + * @var GetListTables + */ + private $getListTables; + + /** + * @var GetViewsBackup + */ + private $getViewsBackup; + /** * @param \Magento\Backup\Model\ResourceModel\Db $resourceDb * @param \Magento\Framework\App\ResourceConnection $resource + * @param GetListTables|null $getListTables + * @param GetViewsBackup|null $getViewsBackup * @param Helper|null $helper */ public function __construct( \Magento\Backup\Model\ResourceModel\Db $resourceDb, \Magento\Framework\App\ResourceConnection $resource, - ?Helper $helper = null + ?Helper $helper = null, + GetListTables $getListTables = null, + GetViewsBackup $getViewsBackup = null ) { $this->_resourceDb = $resourceDb; $this->_resource = $resource; $this->helper = $helper ?? ObjectManager::getInstance()->get(Helper::class); + $this->getListTables = $getListTables ?: ObjectManager::getInstance()->get(GetListTables::class); + $this->getViewsBackup = $getViewsBackup ?: ObjectManager::getInstance()->get(GetViewsBackup::class); } /** @@ -161,7 +179,7 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu $this->getResource()->beginTransaction(); - $tables = $this->getResource()->getTables(); + $tables = $this->getListTables->execute(); $backup->write($this->getResource()->getHeader()); @@ -198,6 +216,8 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu $backup->write($this->getResource()->getTableDataAfterSql($table)); } } + $this->getViewsBackup->execute($backup); + $backup->write($this->getResource()->getTableForeignKeysSql()); $backup->write($this->getResource()->getTableTriggersSql()); $backup->write($this->getResource()->getFooter()); diff --git a/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php b/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php new file mode 100644 index 0000000000000..5a9f96a50c35a --- /dev/null +++ b/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backup\Model\ResourceModel\Table; + +use Magento\Framework\App\ResourceConnection; + +/** + * Class GetListTables + */ +class GetListTables +{ + private const TABLE_TYPE = 'BASE TABLE'; + + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @param ResourceConnection $resource + */ + public function __construct(ResourceConnection $resource) + { + $this->resource = $resource; + } + + /** + * @return array + */ + public function execute() + { + return $this->resource->getConnection('backup')->fetchCol( +"SHOW FULL TABLES WHERE `Table_type` = ?", + self::TABLE_TYPE + ); + } +} diff --git a/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php b/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php new file mode 100644 index 0000000000000..54f2a1a65d280 --- /dev/null +++ b/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backup\Model\ResourceModel\View; + +use Magento\Framework\App\ResourceConnection; + +/** + * Class GetListViews + */ +class GetListViews +{ + private const TABLE_TYPE = 'VIEW'; + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @param ResourceConnection $resource + */ + public function __construct(ResourceConnection $resource) + { + $this->resource = $resource; + } + + /** + * @return array + */ + public function execute() + { + return $this->resource->getConnection('backup')->fetchCol( + "SHOW FULL TABLES WHERE `Table_type` = ?", + self::TABLE_TYPE + ); + } +} diff --git a/app/code/Magento/Backup/Model/ResourceModel/View/GetViewsBackup.php b/app/code/Magento/Backup/Model/ResourceModel/View/GetViewsBackup.php new file mode 100644 index 0000000000000..bda5ff58c0594 --- /dev/null +++ b/app/code/Magento/Backup/Model/ResourceModel/View/GetViewsBackup.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backup\Model\ResourceModel\View; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Backup\Db\BackupInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; + +/** + * Class GetViewsBackup + */ +class GetViewsBackup +{ + /** + * @var GetListViews + */ + private $getListViews; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var AdapterInterface + */ + private $connection; + + /** + * @param GetListViews $getListViews + * @param ResourceConnection $resourceConnection + */ + public function __construct( + GetListViews $getListViews, + ResourceConnection $resourceConnection + ) { + $this->getListViews = $getListViews; + $this->resourceConnection = $resourceConnection; + } + + /** + * @param BackupInterface $backup + */ + public function execute(BackupInterface $backup) + { + $views = $this->getListViews->execute(); + + foreach ($views as $view) { + $backup->write($this->getViewHeader($view)); + $backup->write($this->getDropViewSql($view)); + $backup->write($this->getShowCreateView($view)); + } + } + + /** + * @return AdapterInterface + */ + private function getConnection() + { + if (!$this->connection) { + $this->connection = $this->resourceConnection->getConnection('backup'); + } + + return $this->connection; + } + + /** + * @param string $viewName + * @return string + */ + private function getShowCreateView($viewName) + { + $quotedViewName = $this->getConnection()->quoteIdentifier($viewName); + $query = 'SHOW CREATE VIEW ' . $quotedViewName; + $row = $this->getConnection()->fetchRow($query); + $regExp = '/\sDEFINER\=\`([^`]*)\`\@\`([^`]*)\`/'; + $sql = preg_replace($regExp, '', $row['Create View']); + + return $sql . ';' . "\n"; + } + + /** + * @param string $viewName + * @return string + */ + public function getViewHeader($viewName) + { + $quotedViewName = $this->getConnection()->quoteIdentifier($viewName); + return "\n--\n" . "-- Structure for view {$quotedViewName}\n" . "--\n\n"; + } + + /** + * @param string $viewName + * @return string + */ + public function getDropViewSql($viewName) + { + $quotedViewName = $this->getConnection()->quoteIdentifier($viewName); + return sprintf('DROP VIEW IF EXISTS %s;' . "\n", $quotedViewName); + } +} From 7e45f62f7af0e4af839b66f412402b5a5d84a620 Mon Sep 17 00:00:00 2001 From: Ievgen Shakhsuvarov <ishakhsuvarov@magento.com> Date: Thu, 28 Feb 2019 21:17:59 -0600 Subject: [PATCH 093/108] magento-engcom/magento2ce#2639: Minor coding practices improvements --- app/code/Magento/Backup/Model/Db.php | 21 +++++++------- .../ResourceModel/Table/GetListTables.php | 8 ++++-- ...tViewsBackup.php => CreateViewsBackup.php} | 28 +++++++++++++------ .../Model/ResourceModel/View/GetListViews.php | 7 +++-- 4 files changed, 40 insertions(+), 24 deletions(-) rename app/code/Magento/Backup/Model/ResourceModel/View/{GetViewsBackup.php => CreateViewsBackup.php} (74%) diff --git a/app/code/Magento/Backup/Model/Db.php b/app/code/Magento/Backup/Model/Db.php index a385a476845db..084b35448a823 100644 --- a/app/code/Magento/Backup/Model/Db.php +++ b/app/code/Magento/Backup/Model/Db.php @@ -7,7 +7,7 @@ use Magento\Backup\Helper\Data as Helper; use Magento\Backup\Model\ResourceModel\Table\GetListTables; -use Magento\Backup\Model\ResourceModel\View\GetViewsBackup; +use Magento\Backup\Model\ResourceModel\View\CreateViewsBackup; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\RuntimeException; @@ -51,29 +51,30 @@ class Db implements \Magento\Framework\Backup\Db\BackupDbInterface private $getListTables; /** - * @var GetViewsBackup + * @var CreateViewsBackup */ private $getViewsBackup; /** - * @param \Magento\Backup\Model\ResourceModel\Db $resourceDb + * Db constructor. + * @param ResourceModel\Db $resourceDb * @param \Magento\Framework\App\ResourceConnection $resource - * @param GetListTables|null $getListTables - * @param GetViewsBackup|null $getViewsBackup * @param Helper|null $helper + * @param GetListTables|null $getListTables + * @param CreateViewsBackup|null $getViewsBackup */ public function __construct( - \Magento\Backup\Model\ResourceModel\Db $resourceDb, + ResourceModel\Db $resourceDb, \Magento\Framework\App\ResourceConnection $resource, ?Helper $helper = null, - GetListTables $getListTables = null, - GetViewsBackup $getViewsBackup = null + ?GetListTables $getListTables = null, + ?CreateViewsBackup $getViewsBackup = null ) { $this->_resourceDb = $resourceDb; $this->_resource = $resource; $this->helper = $helper ?? ObjectManager::getInstance()->get(Helper::class); - $this->getListTables = $getListTables ?: ObjectManager::getInstance()->get(GetListTables::class); - $this->getViewsBackup = $getViewsBackup ?: ObjectManager::getInstance()->get(GetViewsBackup::class); + $this->getListTables = $getListTables ?? ObjectManager::getInstance()->get(GetListTables::class); + $this->getViewsBackup = $getViewsBackup ?? ObjectManager::getInstance()->get(CreateViewsBackup::class); } /** diff --git a/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php b/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php index 5a9f96a50c35a..73c4221feba3f 100644 --- a/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php +++ b/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php @@ -10,7 +10,7 @@ use Magento\Framework\App\ResourceConnection; /** - * Class GetListTables + * Provides full list of tables in the database. This list excludes views, to allow different backup process. */ class GetListTables { @@ -30,12 +30,14 @@ public function __construct(ResourceConnection $resource) } /** + * Get list of database tables excluding views. + * * @return array */ - public function execute() + public function execute(): array { return $this->resource->getConnection('backup')->fetchCol( -"SHOW FULL TABLES WHERE `Table_type` = ?", + "SHOW FULL TABLES WHERE `Table_type` = ?", self::TABLE_TYPE ); } diff --git a/app/code/Magento/Backup/Model/ResourceModel/View/GetViewsBackup.php b/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php similarity index 74% rename from app/code/Magento/Backup/Model/ResourceModel/View/GetViewsBackup.php rename to app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php index bda5ff58c0594..51b49dcb9e48a 100644 --- a/app/code/Magento/Backup/Model/ResourceModel/View/GetViewsBackup.php +++ b/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php @@ -12,9 +12,9 @@ use Magento\Framework\DB\Adapter\AdapterInterface; /** - * Class GetViewsBackup + * Creates backup of Views in the database. */ -class GetViewsBackup +class CreateViewsBackup { /** * @var GetListViews @@ -44,23 +44,27 @@ public function __construct( } /** + * Write backup data to backup file. + * * @param BackupInterface $backup */ - public function execute(BackupInterface $backup) + public function execute(BackupInterface $backup): void { $views = $this->getListViews->execute(); foreach ($views as $view) { $backup->write($this->getViewHeader($view)); $backup->write($this->getDropViewSql($view)); - $backup->write($this->getShowCreateView($view)); + $backup->write($this->getCreateView($view)); } } /** + * Retrieve Database connection for Backup. + * * @return AdapterInterface */ - private function getConnection() + private function getConnection(): AdapterInterface { if (!$this->connection) { $this->connection = $this->resourceConnection->getConnection('backup'); @@ -70,10 +74,12 @@ private function getConnection() } /** + * Get CREATE VIEW query for the specific view. + * * @param string $viewName * @return string */ - private function getShowCreateView($viewName) + private function getCreateView(string $viewName): string { $quotedViewName = $this->getConnection()->quoteIdentifier($viewName); $query = 'SHOW CREATE VIEW ' . $quotedViewName; @@ -85,22 +91,26 @@ private function getShowCreateView($viewName) } /** + * Prepare a header for View being dumped. + * * @param string $viewName * @return string */ - public function getViewHeader($viewName) + public function getViewHeader(string $viewName): string { $quotedViewName = $this->getConnection()->quoteIdentifier($viewName); return "\n--\n" . "-- Structure for view {$quotedViewName}\n" . "--\n\n"; } /** + * Make sure that View being created is deleted if already exists. + * * @param string $viewName * @return string */ - public function getDropViewSql($viewName) + public function getDropViewSql(string $viewName): string { $quotedViewName = $this->getConnection()->quoteIdentifier($viewName); - return sprintf('DROP VIEW IF EXISTS %s;' . "\n", $quotedViewName); + return sprintf('DROP VIEW IF EXISTS %s;\n', $quotedViewName); } } diff --git a/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php b/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php index 54f2a1a65d280..c76ea2842180b 100644 --- a/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php +++ b/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php @@ -10,11 +10,12 @@ use Magento\Framework\App\ResourceConnection; /** - * Class GetListViews + * Get list of database views. */ class GetListViews { private const TABLE_TYPE = 'VIEW'; + /** * @var ResourceConnection */ @@ -29,9 +30,11 @@ public function __construct(ResourceConnection $resource) } /** + * Get list of database views. + * * @return array */ - public function execute() + public function execute(): array { return $this->resource->getConnection('backup')->fetchCol( "SHOW FULL TABLES WHERE `Table_type` = ?", From f229cb3a975fdca3d4a4164a106e9cce33737dcd Mon Sep 17 00:00:00 2001 From: Vitaliy Honcharenko <vgoncharenko@magento.com> Date: Fri, 1 Mar 2019 13:13:01 -0600 Subject: [PATCH 094/108] MAGETWO-95294: Mysql search slow on the catalog page - changes after CR --- .../Model/ResourceModel/Advanced/Collection.php | 11 +++++++++-- .../Model/ResourceModel/Fulltext/Collection.php | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php index 0dbcf5f8cf375..e3f61af771f8c 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php @@ -266,7 +266,10 @@ public function setOrder($attribute, $dir = Select::SQL_DESC) */ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) { - /** This changes need in BIC reasons for support dynamic improved algorithm for price aggregation process. */ + /** + * This changes need in backward compatible reasons for support dynamic improved algorithm + * for price aggregation process. + */ if ($this->isCurrentEngineMysql()) { parent::addCategoryFilter($category); } else { @@ -282,7 +285,10 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) */ public function setVisibility($visibility) { - /** This changes need in BIC reasons for support dynamic improved algorithm for price aggregation process. */ + /** + * This changes need in backward compatible reasons for support dynamic improved algorithm + * for price aggregation process. + */ if ($this->isCurrentEngineMysql()) { parent::setVisibility($visibility); } else { @@ -391,6 +397,7 @@ private function getSearchResultApplier(SearchResultInterface $searchResult): Se return $this->searchResultApplierFactory->create([ 'collection' => $this, 'searchResult' => $searchResult, + /** This variable sets by serOrder method, but doesn't have a getter method. */ 'orders' => $this->_orders ]); } diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index ecc25a55a10d7..2c36b150fed07 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -509,6 +509,7 @@ private function getSearchResultApplier(SearchResultInterface $searchResult): Se return $this->searchResultApplierFactory->create([ 'collection' => $this, 'searchResult' => $searchResult, + /** This variable sets by serOrder method, but doesn't have a getter method. */ 'orders' => $this->_orders, ]); } @@ -584,7 +585,10 @@ public function getFacetedData($field) public function addCategoryFilter(\Magento\Catalog\Model\Category $category) { $this->addFieldToFilter('category_ids', $category->getId()); - /** This changes need in BIC reasons for support dynamic improved algorithm for price aggregation process. */ + /** + * This changes need in backward compatible reasons for support dynamic improved algorithm + * for price aggregation process. + */ if ($this->isCurrentEngineMysql()) { parent::addCategoryFilter($category); } else { @@ -603,7 +607,10 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) public function setVisibility($visibility) { $this->addFieldToFilter('visibility', $visibility); - /** This changes need in BIC reasons for support dynamic improved algorithm for price aggregation process. */ + /** + * This changes need in backward compatible reasons for support dynamic improved algorithm + * for price aggregation process. + */ if ($this->isCurrentEngineMysql()) { parent::setVisibility($visibility); } From 0ef771149a3533fb1d800627d3ff44054a416e56 Mon Sep 17 00:00:00 2001 From: Valerii Naida <vnayda@adobe.com> Date: Fri, 1 Mar 2019 14:20:05 -0600 Subject: [PATCH 095/108] GraphQL-418: [Shipping methods] Set Shipping Methods on Cart --- .../Model/Cart/AssignBillingAddressToCart.php | 4 +- .../Cart/AssignShippingAddressToCart.php | 4 +- .../Model/Cart/AssignShippingMethodToCart.php | 82 ++++++++++++++ .../Model/Cart/ExtractDataFromAddress.php | 11 -- .../Model/Cart/GetCustomerAddress.php | 4 +- .../Model/Cart/GetQuoteAddress.php | 88 +++++++++++++++ .../Model/Cart/QuoteAddressFactory.php | 2 +- .../Model/Cart/SetBillingAddressOnCart.php | 18 ++-- ...art.php => SetShippingAddressesOnCart.php} | 12 +-- .../SetShippingAddressesOnCartInterface.php | 4 +- .../Model/Cart/SetShippingMethodOnCart.php | 101 ------------------ .../Model/Cart/SetShippingMethodsOnCart.php | 76 +++++++++++++ .../SetShippingMethodsOnCartInterface.php | 38 +++++++ .../Model/Resolver/ApplyCouponToCart.php | 8 +- .../Model/Resolver/RemoveCouponFromCart.php | 8 +- .../Model/Resolver/RemoveItemFromCart.php | 4 +- .../Model/Resolver/SelectedShippingMethod.php | 43 ++++++++ .../Model/Resolver/SetPaymentMethodOnCart.php | 4 +- .../Resolver/SetShippingMethodsOnCart.php | 61 +++-------- .../Magento/QuoteGraphQl/etc/graphql/di.xml | 4 +- .../Magento/QuoteGraphQl/etc/schema.graphqls | 24 ++--- 21 files changed, 392 insertions(+), 208 deletions(-) create mode 100644 app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingMethodToCart.php create mode 100644 app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php rename app/code/Magento/QuoteGraphQl/Model/Cart/{SetShippingAddressOnCart.php => SetShippingAddressesOnCart.php} (88%) delete mode 100644 app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodOnCart.php create mode 100644 app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php create mode 100644 app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCartInterface.php create mode 100644 app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedShippingMethod.php diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignBillingAddressToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignBillingAddressToCart.php index 370501e9c6e8e..48acc71e0fa37 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignBillingAddressToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignBillingAddressToCart.php @@ -51,9 +51,9 @@ public function execute( try { $this->billingAddressManagement->assign($cart->getId(), $billingAddress, $useForShipping); } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__($e->getMessage())); + throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); } catch (LocalizedException $e) { - throw new GraphQlInputException(__($e->getMessage())); + throw new GraphQlInputException(__($e->getMessage()), $e); } } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingAddressToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingAddressToCart.php index 47f90edb04be8..ee2cd1c961956 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingAddressToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingAddressToCart.php @@ -49,9 +49,9 @@ public function execute( try { $this->shippingAddressManagement->assign($cart->getId(), $shippingAddress); } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__($e->getMessage())); + throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); } catch (LocalizedException $e) { - throw new GraphQlInputException(__($e->getMessage())); + throw new GraphQlInputException(__($e->getMessage()), $e); } } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingMethodToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingMethodToCart.php new file mode 100644 index 0000000000000..8c369b64a76a4 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingMethodToCart.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Cart; + +use Magento\Checkout\Api\Data\ShippingInformationInterface; +use Magento\Checkout\Api\Data\ShippingInformationInterfaceFactory; +use Magento\Checkout\Api\ShippingInformationManagementInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Quote\Api\Data\CartInterface; +use Magento\Quote\Model\Quote\Address as QuoteAddress; + +/** + * Assign shipping method to cart + */ +class AssignShippingMethodToCart +{ + /** + * @var ShippingInformationInterfaceFactory + */ + private $shippingInformationFactory; + + /** + * @var ShippingInformationManagementInterface + */ + private $shippingInformationManagement; + + /** + * @param ShippingInformationInterfaceFactory $shippingInformationFactory + * @param ShippingInformationManagementInterface $shippingInformationManagement + */ + public function __construct( + ShippingInformationInterfaceFactory $shippingInformationFactory, + ShippingInformationManagementInterface $shippingInformationManagement + ) { + $this->shippingInformationFactory = $shippingInformationFactory; + $this->shippingInformationManagement = $shippingInformationManagement; + } + + /** + * Assign shipping method to cart + * + * @param CartInterface $cart + * @param QuoteAddress $quoteAddress + * @param string $carrierCode + * @param string $methodCode + * @throws GraphQlInputException + * @throws GraphQlNoSuchEntityException + */ + public function execute( + CartInterface $cart, + QuoteAddress $quoteAddress, + string $carrierCode, + string $methodCode + ): void { + /** @var ShippingInformationInterface $shippingInformation */ + $shippingInformation = $this->shippingInformationFactory->create([ + 'data' => [ + /* If the address is not a shipping address (but billing) the system will find the proper shipping + address for the selected cart and set the information there (actual for single shipping address) */ + ShippingInformationInterface::SHIPPING_ADDRESS => $quoteAddress, + ShippingInformationInterface::SHIPPING_CARRIER_CODE => $carrierCode, + ShippingInformationInterface::SHIPPING_METHOD_CODE => $methodCode, + ], + ]); + + try { + $this->shippingInformationManagement->saveAddressInformation($cart->getId(), $shippingInformation); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); + } catch (LocalizedException $e) { + throw new GraphQlInputException(__($e->getMessage()), $e); + } + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php index 9d2f1aa9dc875..04fe146008101 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php @@ -40,11 +40,6 @@ public function execute(QuoteAddress $address): array $addressData = $this->dataObjectConverter->toFlatArray($address, [], AddressInterface::class); $addressData['model'] = $address; - if ($address->getShippingMethod()) { - list($carrierCode, $methodCode) = explode('_', $address->getShippingMethod(), 2); - $shippingAmount = $address->getShippingAmount(); - } - $addressData = array_merge($addressData, [ 'address_id' => $address->getId(), 'country' => [ @@ -56,12 +51,6 @@ public function execute(QuoteAddress $address): array 'label' => $address->getRegion() ], 'street' => $address->getStreet(), - 'selected_shipping_method' => [ - 'carrier_code' => $carrierCode ?? null, - 'method_code' => $methodCode ?? null, - 'label' => $address->getShippingDescription(), - 'amount' => $shippingAmount ?? null - ], 'items_weight' => $address->getWeight(), 'customer_notes' => $address->getCustomerNotes() ]); diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCustomerAddress.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCustomerAddress.php index 93c888a1d0bd2..039324abf6854 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCustomerAddress.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCustomerAddress.php @@ -16,7 +16,7 @@ use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; /** - * Get customer address. Throws exception if customer is not owner of address + * Get customer address */ class GetCustomerAddress { @@ -52,7 +52,7 @@ public function execute(int $addressId, int $customerId): AddressInterface __('Could not find a address with ID "%address_id"', ['address_id' => $addressId]) ); } catch (LocalizedException $e) { - throw new GraphQlInputException(__($e->getMessage())); + throw new GraphQlInputException(__($e->getMessage()), $e); } if ((int)$customerAddress->getCustomerId() !== $customerId) { diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php new file mode 100644 index 0000000000000..b9900da420bd5 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Cart; + +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Quote\Model\Quote\Address as QuoteAddress; +use Magento\Quote\Model\Quote\AddressFactory as QuoteAddressFactory; +use Magento\Quote\Model\ResourceModel\Quote\Address as QuoteAddressResource; + + +/** + * Get quote address + */ +class GetQuoteAddress +{ + /** + * @var QuoteAddressFactory + */ + private $quoteAddressFactory; + + /** + * @var QuoteAddressResource + */ + private $quoteAddressResource; + + /** + * @var AddressRepositoryInterface + */ + private $addressRepository; + + /** + * @param AddressRepositoryInterface $addressRepository + */ + public function __construct(AddressRepositoryInterface $addressRepository) + { + $this->addressRepository = $addressRepository; + } + + /** + * Get quote address + * + * @param int $quoteAddressId + * @param int|null $customerId + * @return AddressInterface + * @throws GraphQlInputException + * @throws GraphQlNoSuchEntityException + * @throws GraphQlAuthorizationException + */ + public function execute(int $quoteAddressId, ?int $customerId): QuoteAddress + { + $quoteAddress = $this->quoteAddressFactory->create(); + + $this->quoteAddressResource->load($quoteAddress, $quoteAddressId); + if (null === $quoteAddress->getId()) { + throw new GraphQlNoSuchEntityException( + __('Could not find a cart address with ID "%cart_address_id"', ['cart_address_id' => $quoteAddressId]) + ); + } + + $quoteAddressCustomerId = (int)$quoteAddress->getCustomerId(); + + /* Guest cart, allow operations */ + if (!$quoteAddressCustomerId && null === $customerId) { + return $quoteAddress; + } + + if ($quoteAddressCustomerId !== $customerId) { + throw new GraphQlAuthorizationException( + __( + 'The current user cannot use cart address with ID "%cart_address_id"', + ['cart_address_id' => $quoteAddressId] + ) + ); + } + return $quoteAddress; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php b/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php index 7dfea0836e8d6..76bdc74611131 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php @@ -60,7 +60,7 @@ public function createBasedOnCustomerAddress(CustomerAddress $customerAddress): try { $quoteAddress->importCustomerAddressData($customerAddress); } catch (LocalizedException $e) { - throw new GraphQlInputException(__($e->getMessage())); + throw new GraphQlInputException(__($e->getMessage()), $e); } return $quoteAddress; } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php index 04b7bfcfe0e62..d927a695696f3 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php @@ -63,19 +63,19 @@ public function __construct( * * @param ContextInterface $context * @param CartInterface $cart - * @param array $billingAddress + * @param array $billingAddressInput * @return void * @throws GraphQlInputException * @throws GraphQlAuthenticationException * @throws GraphQlAuthorizationException * @throws GraphQlNoSuchEntityException */ - public function execute(ContextInterface $context, CartInterface $cart, array $billingAddress): void + public function execute(ContextInterface $context, CartInterface $cart, array $billingAddressInput): void { - $customerAddressId = $billingAddress['customer_address_id'] ?? null; - $addressInput = $billingAddress['address'] ?? null; - $useForShipping = isset($billingAddress['use_for_shipping']) - ? (bool)$billingAddress['use_for_shipping'] : false; + $customerAddressId = $billingAddressInput['customer_address_id'] ?? null; + $addressInput = $billingAddressInput['address'] ?? null; + $useForShipping = isset($billingAddressInput['use_for_shipping']) + ? (bool)$billingAddressInput['use_for_shipping'] : false; if (null === $customerAddressId && null === $addressInput) { throw new GraphQlInputException( @@ -97,13 +97,13 @@ public function execute(ContextInterface $context, CartInterface $cart, array $b } if (null === $customerAddressId) { - $billingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput); + $billingAddressInput = $this->quoteAddressFactory->createBasedOnInputData($addressInput); } else { $this->checkCustomerAccount->execute($context->getUserId(), $context->getUserType()); $customerAddress = $this->getCustomerAddress->execute((int)$customerAddressId, (int)$context->getUserId()); - $billingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress($customerAddress); + $billingAddressInput = $this->quoteAddressFactory->createBasedOnCustomerAddress($customerAddress); } - $this->assignBillingAddressToCart->execute($cart, $billingAddress, $useForShipping); + $this->assignBillingAddressToCart->execute($cart, $billingAddressInput, $useForShipping); } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php similarity index 88% rename from app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressOnCart.php rename to app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php index 1a14424853491..8e54ab0d3feef 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php @@ -15,7 +15,7 @@ /** * Set single shipping address for a specified shopping cart */ -class SetShippingAddressOnCart implements SetShippingAddressesOnCartInterface +class SetShippingAddressesOnCart implements SetShippingAddressesOnCartInterface { /** * @var QuoteAddressFactory @@ -58,16 +58,16 @@ public function __construct( /** * @inheritdoc */ - public function execute(ContextInterface $context, CartInterface $cart, array $shippingAddresses): void + public function execute(ContextInterface $context, CartInterface $cart, array $shippingAddressesInput): void { - if (count($shippingAddresses) > 1) { + if (count($shippingAddressesInput) > 1) { throw new GraphQlInputException( __('You cannot specify multiple shipping addresses.') ); } - $shippingAddress = current($shippingAddresses); - $customerAddressId = $shippingAddress['customer_address_id'] ?? null; - $addressInput = $shippingAddress['address'] ?? null; + $shippingAddressInput = current($shippingAddressesInput); + $customerAddressId = $shippingAddressInput['customer_address_id'] ?? null; + $addressInput = $shippingAddressInput['address'] ?? null; if (null === $customerAddressId && null === $addressInput) { throw new GraphQlInputException( diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCartInterface.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCartInterface.php index 81da47933e812..eb0f3522102cf 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCartInterface.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCartInterface.php @@ -27,12 +27,12 @@ interface SetShippingAddressesOnCartInterface * * @param ContextInterface $context * @param CartInterface $cart - * @param array $shippingAddresses + * @param array $shippingAddressesInput * @return void * @throws GraphQlInputException * @throws GraphQlAuthorizationException * @throws GraphQlAuthenticationException * @throws GraphQlNoSuchEntityException */ - public function execute(ContextInterface $context, CartInterface $cart, array $shippingAddresses): void; + public function execute(ContextInterface $context, CartInterface $cart, array $shippingAddressesInput): void; } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodOnCart.php deleted file mode 100644 index a630b2d07c7df..0000000000000 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodOnCart.php +++ /dev/null @@ -1,101 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\QuoteGraphQl\Model\Cart; - -use Magento\Framework\Exception\InputException; -use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\Exception\StateException; -use Magento\Framework\GraphQl\Exception\GraphQlInputException; -use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; -use Magento\Quote\Model\Quote; -use Magento\Quote\Model\Quote\AddressFactory as QuoteAddressFactory; -use Magento\Quote\Model\ResourceModel\Quote\Address as QuoteAddressResource; -use Magento\Checkout\Model\ShippingInformationFactory; -use Magento\Checkout\Api\ShippingInformationManagementInterface; -use Magento\Checkout\Model\ShippingInformation; - -/** - * Class SetShippingMethodsOnCart - * - * Set shipping method for a specified shopping cart address - */ -class SetShippingMethodOnCart -{ - /** - * @var ShippingInformationFactory - */ - private $shippingInformationFactory; - - /** - * @var QuoteAddressFactory - */ - private $quoteAddressFactory; - - /** - * @var QuoteAddressResource - */ - private $quoteAddressResource; - - /** - * @var ShippingInformationManagementInterface - */ - private $shippingInformationManagement; - - /** - * @param ShippingInformationManagementInterface $shippingInformationManagement - * @param QuoteAddressFactory $quoteAddressFactory - * @param QuoteAddressResource $quoteAddressResource - * @param ShippingInformationFactory $shippingInformationFactory - */ - public function __construct( - ShippingInformationManagementInterface $shippingInformationManagement, - QuoteAddressFactory $quoteAddressFactory, - QuoteAddressResource $quoteAddressResource, - ShippingInformationFactory $shippingInformationFactory - ) { - $this->shippingInformationManagement = $shippingInformationManagement; - $this->quoteAddressResource = $quoteAddressResource; - $this->quoteAddressFactory = $quoteAddressFactory; - $this->shippingInformationFactory = $shippingInformationFactory; - } - - /** - * Sets shipping method for a specified shopping cart address - * - * @param Quote $cart - * @param int $cartAddressId - * @param string $carrierCode - * @param string $methodCode - * @throws GraphQlInputException - * @throws GraphQlNoSuchEntityException - */ - public function execute(Quote $cart, int $cartAddressId, string $carrierCode, string $methodCode): void - { - $quoteAddress = $this->quoteAddressFactory->create(); - $this->quoteAddressResource->load($quoteAddress, $cartAddressId); - - /** @var ShippingInformation $shippingInformation */ - $shippingInformation = $this->shippingInformationFactory->create(); - - /* If the address is not a shipping address (but billing) the system will find the proper shipping address for - the selected cart and set the information there (actual for single shipping address) */ - $shippingInformation->setShippingAddress($quoteAddress); - $shippingInformation->setShippingCarrierCode($carrierCode); - $shippingInformation->setShippingMethodCode($methodCode); - - try { - $this->shippingInformationManagement->saveAddressInformation($cart->getId(), $shippingInformation); - } catch (NoSuchEntityException $exception) { - throw new GraphQlNoSuchEntityException(__($exception->getMessage())); - } catch (StateException $exception) { - throw new GraphQlInputException(__($exception->getMessage())); - } catch (InputException $exception) { - throw new GraphQlInputException(__($exception->getMessage())); - } - } -} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php new file mode 100644 index 0000000000000..66a7608874f9d --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Cart; + +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\StateException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; +use Magento\Quote\Api\Data\CartInterface; + +/** + * Set single shipping method for a specified shopping cart + */ +class SetShippingMethodsOnCart implements SetShippingMethodsOnCartInterface +{ + /** + * @var GetQuoteAddress + */ + private $getQuoteAddress; + + /** + * @var AssignShippingMethodToCart + */ + private $assignShippingMethodToCart; + + /** + * @param GetQuoteAddress $getQuoteAddress + * @param AssignShippingMethodToCart $assignShippingMethodToCart + */ + public function __construct( + GetQuoteAddress $getQuoteAddress, + AssignShippingMethodToCart $assignShippingMethodToCart + ) { + $this->getQuoteAddress = $getQuoteAddress; + $this->assignShippingMethodToCart = $assignShippingMethodToCart; + } + + /** + * @inheritdoc + */ + public function execute(ContextInterface $context, CartInterface $cart, array $shippingMethodsInput): void + { + if (count($shippingMethodsInput) > 1) { + throw new GraphQlInputException( + __('You cannot specify multiple shipping methods.') + ); + } + $shippingMethodInput = current($shippingMethodsInput); + + if (!isset($shippingMethodInput['cart_address_id']) || empty($shippingMethodInput['cart_address_id'])) { + throw new GraphQlInputException(__('Required parameter "cart_address_id" is missing.')); + } + $cartAddressId = $shippingMethodInput['cart_address_id']; + + if (!isset($shippingMethodInput['carrier_code']) || empty($shippingMethodInput['carrier_code'])) { + throw new GraphQlInputException(__('Required parameter "carrier_code" is missing.')); + } + $carrierCode = $shippingMethodInput['carrier_code']; + + if (!isset($shippingMethodInput['method_code']) || empty($shippingMethodInput['method_code'])) { + throw new GraphQlInputException(__('Required parameter "method_code" is missing.')); + } + $methodCode = $shippingMethodInput['method_code']; + + $quoteAddress = $this->getQuoteAddress->execute($cartAddressId); + + $this->assignShippingMethodToCart->execute($cart, $quoteAddress, $carrierCode, $methodCode); + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCartInterface.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCartInterface.php new file mode 100644 index 0000000000000..fa6c6cf0923e4 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCartInterface.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Cart; + +use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; +use Magento\Quote\Api\Data\CartInterface; + +/** + * Extension point for setting shipping methods for a specified shopping cart + * + * All objects that are responsible for setting shipping methods on a cart via GraphQl + * should implement this interface. + */ +interface SetShippingMethodsOnCartInterface +{ + /** + * Set shipping methods for a specified shopping cart + * + * @param ContextInterface $context + * @param CartInterface $cart + * @param array $shippingMethodsInput + * @return void + * @throws GraphQlInputException + * @throws GraphQlAuthorizationException + * @throws GraphQlAuthenticationException + * @throws GraphQlNoSuchEntityException + */ + public function execute(ContextInterface $context, CartInterface $cart, array $shippingMethodsInput): void; +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php index a334e7482ca47..0af35354758c9 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php @@ -74,10 +74,10 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value try { $this->couponManagement->set($cartId, $couponCode); - } catch (NoSuchEntityException $exception) { - throw new GraphQlNoSuchEntityException(__($exception->getMessage())); - } catch (CouldNotSaveException $exception) { - throw new LocalizedException(__($exception->getMessage())); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); + } catch (CouldNotSaveException $e) { + throw new LocalizedException(__($e->getMessage()), $e); } return [ diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php index 730c927c32a0c..68925c6e8944d 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php @@ -61,10 +61,10 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value try { $this->couponManagement->remove($cartId); - } catch (NoSuchEntityException $exception) { - throw new GraphQlNoSuchEntityException(__($exception->getMessage())); - } catch (CouldNotDeleteException $exception) { - throw new LocalizedException(__($exception->getMessage())); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); + } catch (CouldNotDeleteException $e) { + throw new LocalizedException(__($e->getMessage()), $e); } return [ diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php index 1838f17369ffc..c8f0ef06c7594 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php @@ -64,9 +64,9 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value try { $this->guestCartItemRepository->deleteById($maskedCartId, $itemId); } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__($e->getMessage())); + throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); } catch (LocalizedException $e) { - throw new GraphQlInputException(__($e->getMessage())); + throw new GraphQlInputException(__($e->getMessage()), $e); } return [ diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedShippingMethod.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedShippingMethod.php new file mode 100644 index 0000000000000..06e9594fe2388 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedShippingMethod.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Resolver; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * @inheritdoc + */ +class SelectedShippingMethod implements ResolverInterface +{ + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" value should be specified')); + } + + $address = $value['model']; + + if ($address->getShippingMethod()) { + list($carrierCode, $methodCode) = explode('_', $address->getShippingMethod(), 2); + $shippingAmount = $address->getShippingAmount(); + } + + return [ + 'carrier_code' => $carrierCode ?? null, + 'method_code' => $methodCode ?? null, + 'label' => $address->getShippingDescription(), + 'amount' => $shippingAmount ?? null, + ]; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php index ffd1bf37f4771..a93c8032c996a 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php @@ -86,9 +86,9 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value try { $this->paymentMethodManagement->set($cart->getId(), $payment); } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__($e->getMessage())); + throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); } catch (LocalizedException $e) { - throw new GraphQlInputException(__($e->getMessage())); + throw new GraphQlInputException(__($e->getMessage()), $e); } return [ diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php index eedd6cb925fa0..e69ba47e7adf5 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php @@ -11,9 +11,8 @@ use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Framework\Stdlib\ArrayManager; use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; -use Magento\QuoteGraphQl\Model\Cart\SetShippingMethodOnCart; +use Magento\QuoteGraphQl\Model\Cart\SetShippingMethodsOnCartInterface; /** * Mutation resolver for setting shipping methods for shopping cart @@ -21,33 +20,25 @@ class SetShippingMethodsOnCart implements ResolverInterface { /** - * @var SetShippingMethodOnCart - */ - private $setShippingMethodOnCart; - - /** - * @var ArrayManager + * @var GetCartForUser */ - private $arrayManager; + private $getCartForUser; /** - * @var GetCartForUser + * @var SetShippingMethodsOnCartInterface */ - private $getCartForUser; + private $setShippingMethodsOnCart; /** - * @param ArrayManager $arrayManager * @param GetCartForUser $getCartForUser - * @param SetShippingMethodOnCart $setShippingMethodOnCart + * @param SetShippingMethodsOnCartInterface $setShippingMethodsOnCart */ public function __construct( - ArrayManager $arrayManager, GetCartForUser $getCartForUser, - SetShippingMethodOnCart $setShippingMethodOnCart + SetShippingMethodsOnCartInterface $setShippingMethodsOnCart ) { - $this->arrayManager = $arrayManager; $this->getCartForUser = $getCartForUser; - $this->setShippingMethodOnCart = $setShippingMethodOnCart; + $this->setShippingMethodsOnCart = $setShippingMethodsOnCart; } /** @@ -55,40 +46,18 @@ public function __construct( */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { - $shippingAddresses = $this->arrayManager->get('input/shipping_addresses', $args); - $maskedCartId = $this->arrayManager->get('input/cart_id', $args); - - if (!$maskedCartId) { + if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) { throw new GraphQlInputException(__('Required parameter "cart_id" is missing')); } - if (!$shippingAddresses) { - throw new GraphQlInputException(__('Required parameter "shipping_methods" is missing')); - } + $maskedCartId = $args['input']['cart_id']; - $shippingAddress = reset($shippingAddresses); // This point can be extended for multishipping - - if (!$shippingAddress['cart_address_id']) { - throw new GraphQlInputException(__('Required parameter "cart_address_id" is missing')); - } - if (!isset($shippingAddress['shipping_method'])) { - throw new GraphQlInputException(__('Required parameter "shipping_method" is missing')); - } - if (!$shippingAddress['shipping_method']['carrier_code']) { - throw new GraphQlInputException(__('Required parameter "carrier_code" is missing')); - } - if (!$shippingAddress['shipping_method']['method_code']) { - throw new GraphQlInputException(__('Required parameter "method_code" is missing')); + if (!isset($args['input']['shipping_methods']) || empty($args['input']['shipping_methods'])) { + throw new GraphQlInputException(__('Required parameter "shipping_methods" is missing')); } + $shippingMethods = $args['input']['shipping_methods']; - $userId = $context->getUserId(); - $cart = $this->getCartForUser->execute((string) $maskedCartId, $userId); - - $this->setShippingMethodOnCart->execute( - $cart, - $shippingAddress['cart_address_id'], - $shippingAddress['shipping_method']['carrier_code'], - $shippingAddress['shipping_method']['method_code'] - ); + $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId()); + $this->setShippingMethodsOnCart->execute($context, $cart, $shippingMethods); return [ 'cart' => [ diff --git a/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml b/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml index 86bc954ae4ac4..c7389cf667845 100644 --- a/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml @@ -7,5 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\QuoteGraphQl\Model\Cart\SetShippingAddressesOnCartInterface" - type="Magento\QuoteGraphQl\Model\Cart\SetShippingAddressOnCart" /> + type="Magento\QuoteGraphQl\Model\Cart\SetShippingAddressesOnCart"/> + <preference for="Magento\QuoteGraphQl\Model\Cart\SetShippingMethodsOnCartInterface" + type="Magento\QuoteGraphQl\Model\Cart\SetShippingMethodsOnCart"/> </config> diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index a1dd87e25ff79..7328a328ce2bf 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -66,12 +66,6 @@ input SetShippingAddressesOnCartInput { input ShippingAddressInput { customer_address_id: Int # If provided then will be used address from address book address: CartAddressInput - cart_items: [CartItemQuantityInput!] -} - -input CartItemQuantityInput { - cart_item_id: Int! - quantity: Float! } input SetBillingAddressOnCartInput { @@ -100,17 +94,17 @@ input CartAddressInput { input SetShippingMethodsOnCartInput { cart_id: String! - shipping_addresses: [ShippingMethodForAddressInput!]! -} - -input ShippingMethodForAddressInput { - cart_address_id: Int! - shipping_method: ShippingMethodInput! + shipping_methods: [ShippingMethodInput!]! } input ShippingMethodInput { + cart_address_id: Int! carrier_code: String! method_code: String! + additional_data: ShippingMethodAdditionalDataInput +} + +input ShippingMethodAdditionalDataInput { } input SetPaymentMethodOnCartInput { @@ -169,7 +163,7 @@ type CartAddress { telephone: String address_type: AdressTypeEnum available_shipping_methods: [AvailableShippingMethod] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAdress\\AvailableShippingMethods") - selected_shipping_method: SelectedShippingMethod + selected_shipping_method: SelectedShippingMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAdress\\SelectedShippingMethod") items_weight: Float customer_notes: String cart_items: [CartItemQuantity] @@ -195,6 +189,10 @@ type SelectedShippingMethod { method_code: String label: String amount: Float + additional_data: SelectedShippingMethodAdditionalData +} + +type SelectedShippingMethodAdditionalData { } type AvailableShippingMethod { From e02dc94e04228901706936c2cac778679d5dc346 Mon Sep 17 00:00:00 2001 From: Valerii Naida <vnayda@adobe.com> Date: Fri, 1 Mar 2019 15:21:04 -0600 Subject: [PATCH 096/108] GraphQL-418: [Shipping methods] Set Shipping Methods on Cart --- .../Model/Cart/AssignBillingAddressToCart.php | 6 ++-- .../Cart/AssignShippingAddressToCart.php | 6 ++-- .../Model/Cart/AssignShippingMethodToCart.php | 6 ++-- .../Model/Cart/GetQuoteAddress.php | 34 ++++++++----------- .../Model/Cart/SetBillingAddressOnCart.php | 6 ++-- .../Model/Cart/SetShippingMethodsOnCart.php | 2 +- 6 files changed, 27 insertions(+), 33 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignBillingAddressToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignBillingAddressToCart.php index 48acc71e0fa37..dd6478b4873c6 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignBillingAddressToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignBillingAddressToCart.php @@ -11,9 +11,9 @@ use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Quote\Api\Data\AddressInterface; use Magento\Quote\Api\Data\CartInterface; use Magento\Quote\Api\BillingAddressManagementInterface; -use Magento\Quote\Model\Quote\Address as QuoteAddress; /** * Set billing address for a specified shopping cart @@ -38,14 +38,14 @@ public function __construct( * Assign billing address to cart * * @param CartInterface $cart - * @param QuoteAddress $billingAddress + * @param AddressInterface $billingAddress * @param bool $useForShipping * @throws GraphQlInputException * @throws GraphQlNoSuchEntityException */ public function execute( CartInterface $cart, - QuoteAddress $billingAddress, + AddressInterface $billingAddress, bool $useForShipping ): void { try { diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingAddressToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingAddressToCart.php index ee2cd1c961956..527999b245a4c 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingAddressToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingAddressToCart.php @@ -11,8 +11,8 @@ use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Quote\Api\Data\AddressInterface; use Magento\Quote\Api\Data\CartInterface; -use Magento\Quote\Model\Quote\Address as QuoteAddress; use Magento\Quote\Model\ShippingAddressManagementInterface; /** @@ -38,13 +38,13 @@ public function __construct( * Assign shipping address to cart * * @param CartInterface $cart - * @param QuoteAddress $shippingAddress + * @param AddressInterface $shippingAddress * @throws GraphQlInputException * @throws GraphQlNoSuchEntityException */ public function execute( CartInterface $cart, - QuoteAddress $shippingAddress + AddressInterface $shippingAddress ): void { try { $this->shippingAddressManagement->assign($cart->getId(), $shippingAddress); diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingMethodToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingMethodToCart.php index 8c369b64a76a4..5b30c0774c22f 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingMethodToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingMethodToCart.php @@ -14,8 +14,8 @@ use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Quote\Api\Data\AddressInterface; use Magento\Quote\Api\Data\CartInterface; -use Magento\Quote\Model\Quote\Address as QuoteAddress; /** * Assign shipping method to cart @@ -48,7 +48,7 @@ public function __construct( * Assign shipping method to cart * * @param CartInterface $cart - * @param QuoteAddress $quoteAddress + * @param AddressInterface $quoteAddress * @param string $carrierCode * @param string $methodCode * @throws GraphQlInputException @@ -56,7 +56,7 @@ public function __construct( */ public function execute( CartInterface $cart, - QuoteAddress $quoteAddress, + AddressInterface $quoteAddress, string $carrierCode, string $methodCode ): void { diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php index b9900da420bd5..1fb737d964139 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php @@ -7,17 +7,12 @@ namespace Magento\QuoteGraphQl\Model\Cart; -use Magento\Customer\Api\AddressRepositoryInterface; -use Magento\Customer\Api\Data\AddressInterface; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; -use Magento\Quote\Model\Quote\Address as QuoteAddress; -use Magento\Quote\Model\Quote\AddressFactory as QuoteAddressFactory; -use Magento\Quote\Model\ResourceModel\Quote\Address as QuoteAddressResource; - +use Magento\Quote\Api\Data\AddressInterface; +use Magento\Quote\Api\Data\AddressInterfaceFactory; +use Magento\Quote\Model\ResourceModel\Quote\Address as AddressResource; /** * Get quote address @@ -25,26 +20,25 @@ class GetQuoteAddress { /** - * @var QuoteAddressFactory + * @var AddressInterfaceFactory */ private $quoteAddressFactory; /** - * @var QuoteAddressResource + * @var AddressResource */ private $quoteAddressResource; /** - * @var AddressRepositoryInterface + * @param AddressInterfaceFactory $quoteAddressFactory + * @param AddressResource $quoteAddressResource */ - private $addressRepository; - - /** - * @param AddressRepositoryInterface $addressRepository - */ - public function __construct(AddressRepositoryInterface $addressRepository) - { - $this->addressRepository = $addressRepository; + public function __construct( + AddressInterfaceFactory $quoteAddressFactory, + AddressResource $quoteAddressResource + ) { + $this->quoteAddressFactory = $quoteAddressFactory; + $this->quoteAddressResource = $quoteAddressResource; } /** @@ -57,7 +51,7 @@ public function __construct(AddressRepositoryInterface $addressRepository) * @throws GraphQlNoSuchEntityException * @throws GraphQlAuthorizationException */ - public function execute(int $quoteAddressId, ?int $customerId): QuoteAddress + public function execute(int $quoteAddressId, ?int $customerId): AddressInterface { $quoteAddress = $this->quoteAddressFactory->create(); diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php index d927a695696f3..cf277c729cdfd 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php @@ -97,13 +97,13 @@ public function execute(ContextInterface $context, CartInterface $cart, array $b } if (null === $customerAddressId) { - $billingAddressInput = $this->quoteAddressFactory->createBasedOnInputData($addressInput); + $billingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput); } else { $this->checkCustomerAccount->execute($context->getUserId(), $context->getUserType()); $customerAddress = $this->getCustomerAddress->execute((int)$customerAddressId, (int)$context->getUserId()); - $billingAddressInput = $this->quoteAddressFactory->createBasedOnCustomerAddress($customerAddress); + $billingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress($customerAddress); } - $this->assignBillingAddressToCart->execute($cart, $billingAddressInput, $useForShipping); + $this->assignBillingAddressToCart->execute($cart, $billingAddress, $useForShipping); } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php index 66a7608874f9d..37e0118423745 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php @@ -69,7 +69,7 @@ public function execute(ContextInterface $context, CartInterface $cart, array $s } $methodCode = $shippingMethodInput['method_code']; - $quoteAddress = $this->getQuoteAddress->execute($cartAddressId); + $quoteAddress = $this->getQuoteAddress->execute($cartAddressId, $context->getUserId()); $this->assignShippingMethodToCart->execute($cart, $quoteAddress, $carrierCode, $methodCode); } From cbc40cf6158ddf53f8f1b1e35b2a034ee77c649d Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza <enarc@atwix.com> Date: Sun, 3 Mar 2019 08:55:25 +0100 Subject: [PATCH 097/108] Tests coverage --- .../GraphQl/Catalog/ProductViewTest.php | 25 ++++++++++- .../product_simple_with_full_option_set.php | 44 +++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) 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 a7c83aba89f0a..e91328267fcc0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php @@ -137,6 +137,26 @@ public function testQueryAllFieldsSimpleProduct() sort_order } } + ... on CustomizableCheckboxOption { + checkbox_option: value { + option_type_id + sku + price + price_type + title + sort_order + } + } + ... on CustomizableMultipleOption { + multiple_option: value { + option_type_id + sku + price + price_type + title + sort_order + } + } ...on CustomizableFileOption { product_sku file_option: value { @@ -278,6 +298,7 @@ public function testQueryAllFieldsSimpleProduct() /** * @magentoApiDataFixture Magento/Catalog/_files/product_simple_with_media_gallery_entries.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @gro */ public function testQueryMediaGalleryEntryFieldsSimpleProduct() { @@ -736,7 +757,7 @@ private function assertOptions($product, $actualResponse) $values = $option->getValues(); /** @var \Magento\Catalog\Model\Product\Option\Value $value */ $value = current($values); - $findValueKeyName = $option->getType() === 'radio' ? 'radio_option' : 'drop_down_option'; + $findValueKeyName = $option->getType() . '_option'; if ($value->getTitle() === $optionsArray[$findValueKeyName][0]['title']) { $match = true; } @@ -756,7 +777,7 @@ private function assertOptions($product, $actualResponse) ]; if (!empty($option->getValues())) { - $valueKeyName = $option->getType() === 'radio' ? 'radio_option' : 'drop_down_option'; + $valueKeyName = $option->getType() . '_option'; $value = current($optionsArray[$valueKeyName]); /** @var \Magento\Catalog\Model\Product\Option\Value $productValue */ $productValue = current($option->getValues()); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_full_option_set.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_full_option_set.php index bd5dc541a15a5..5facba07d58f1 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_full_option_set.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_full_option_set.php @@ -187,6 +187,50 @@ 'price_type' => 'percent', 'sku' => 'sku2', 'max_characters' => 20, + ], + [ + 'title' => 'multiple option', + 'type' => 'multiple', + 'is_require' => true, + 'sort_order' => 7, + 'values' => [ + [ + 'title' => 'multiple option 1', + 'price' => 10, + 'price_type' => 'fixed', + 'sku' => 'multiple option 1 sku', + 'sort_order' => 1, + ], + [ + 'title' => 'multiple option 2', + 'price' => 20, + 'price_type' => 'fixed', + 'sku' => 'multiple option 2 sku', + 'sort_order' => 2, + ], + ], + ], + [ + 'title' => 'checkbox option', + 'type' => 'checkbox', + 'is_require' => true, + 'sort_order' => 6, + 'values' => [ + [ + 'title' => 'checkbox option 1', + 'price' => 10, + 'price_type' => 'fixed', + 'sku' => 'checkbox option 1 sku', + 'sort_order' => 1, + ], + [ + 'title' => 'checkbox option 2', + 'price' => 20, + 'price_type' => 'fixed', + 'sku' => 'checkbox option 2 sku', + 'sort_order' => 2, + ], + ], ] ]; From 73d1c96423459e736f0f0bc69f7b13380ec86900 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza <enarc@atwix.com> Date: Sun, 3 Mar 2019 08:56:53 +0100 Subject: [PATCH 098/108] Removed unused annotation --- .../testsuite/Magento/GraphQl/Catalog/ProductViewTest.php | 1 - 1 file changed, 1 deletion(-) 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 e91328267fcc0..e517b22ad09de 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php @@ -298,7 +298,6 @@ public function testQueryAllFieldsSimpleProduct() /** * @magentoApiDataFixture Magento/Catalog/_files/product_simple_with_media_gallery_entries.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @gro */ public function testQueryMediaGalleryEntryFieldsSimpleProduct() { From 9c9be751a1bea2dbb9a709271e283b15355ae5e0 Mon Sep 17 00:00:00 2001 From: Vitaliy Honcharenko <vgoncharenko@magento.com> Date: Mon, 4 Mar 2019 11:35:39 -0600 Subject: [PATCH 099/108] MAGETWO-95294: Mysql search slow on the catalog page - changes after merge --- .../Resolver/DefaultResolverTest.php | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php index c2a70d3f082f0..a3c6e7e148f3d 100644 --- a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php +++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php @@ -66,6 +66,7 @@ protected function setUp() * @param $fieldType * @param $attributeCode * @param $frontendInput + * @param $isSortable * @param $context * @param $expected * @return void @@ -74,6 +75,7 @@ public function testGetFieldName( $fieldType, $attributeCode, $frontendInput, + $isSortable, $context, $expected ) { @@ -82,7 +84,7 @@ public function testGetFieldName( ->willReturn('string'); $attributeMock = $this->getMockBuilder(AttributeAdapter::class) ->disableOriginalConstructor() - ->setMethods(['getAttributeCode', 'getFrontendInput']) + ->setMethods(['getAttributeCode', 'getFrontendInput', 'isSortable']) ->getMock(); $attributeMock->expects($this->any()) ->method('getAttributeCode') @@ -90,6 +92,9 @@ public function testGetFieldName( $attributeMock->expects($this->any()) ->method('getFrontendInput') ->willReturn($frontendInput); + $attributeMock->expects($this->any()) + ->method('isSortable') + ->willReturn($isSortable); $this->fieldTypeResolver->expects($this->any()) ->method('getFieldType') ->willReturn($fieldType); @@ -106,13 +111,13 @@ public function testGetFieldName( public function getFieldNameProvider() { return [ - ['', 'code', '', [], 'code'], - ['', 'code', '', ['type' => 'default'], 'code'], - ['string', '*', '', ['type' => 'default'], '_search'], - ['', 'code', '', ['type' => 'default'], 'code'], - ['', 'code', 'select', ['type' => 'default'], 'code'], - ['', 'code', 'boolean', ['type' => 'default'], 'code'], - ['', 'code', '', ['type' => 'type'], 'sort_code'], + ['', 'code', '', false, [], 'code'], + ['', 'code', '', false, ['type' => 'default'], 'code'], + ['string', '*', '', false, ['type' => 'default'], '_search'], + ['', 'code', '', false, ['type' => 'default'], 'code'], + ['', 'code', 'select', false, ['type' => 'default'], 'code'], + ['', 'code', 'boolean', false, ['type' => 'default'], 'code'], + ['', 'code', '', true, ['type' => 'sort'], 'sort_code'], ]; } } From f2f05df5bf954913f99cd12b4907f7fbc5a15850 Mon Sep 17 00:00:00 2001 From: Valerii Naida <vnayda@adobe.com> Date: Mon, 4 Mar 2019 11:41:09 -0600 Subject: [PATCH 100/108] GraphQL-418: [Shipping methods] Set Shipping Methods on Cart --- .../Customer/SetShippingMethodsOnCartTest.php | 193 ++++++++++ .../Guest/SetShippingMethodsOnCartTest.php | 160 ++++++++ .../Quote/SetShippingMethodsOnCartTest.php | 356 ------------------ 3 files changed, 353 insertions(+), 356 deletions(-) create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php delete mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php new file mode 100644 index 0000000000000..6fe899145f369 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php @@ -0,0 +1,193 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for setting shipping methods on cart for customer + */ +class SetShippingMethodsOnCartTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var QuoteFactory + */ + private $quoteFactory; + + /** + * @var QuoteIdToMaskedQuoteIdInterface + */ + private $quoteIdToMaskedId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->quoteResource = $objectManager->get(QuoteResource::class); + $this->quoteFactory = $objectManager->get(QuoteFactory::class); + $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + public function testShippingMethodWithVirtualProduct() + { + + } + + public function testShippingMethodWithSimpleProduct() + { + + } + + public function testShippingMethodWithSimpleProductWithoutAddress() + { + + } + + public function testSetShippingMethodWithMissedRequiredParameters() + { + + } + + public function testSetNonExistentShippingMethod() + { + } + + public function testSetShippingMethodIfAddressIsNotBelongToCart() + { + } + + public function testSetShippingMethodToNonExistentCart() + { + } + + public function testSetShippingMethodToGuestCart() + { + + } + + public function testSetShippingMethodToAnotherCustomerCart() + { + + } + + public function testSetShippingMethodToNonExistentCartAddress() + { + } + + public function testSetShippingMethodToGuestCartAddress() + { + + } + + public function testSetShippingMethodToAnotherCustomerCartAddress() + { + + } + + /** + * @param string $maskedQuoteId + * @param string $shippingMethodCode + * @param string $shippingCarrierCode + * @param string $shippingAddressId + * @return string + */ + private function prepareMutationQuery( + string $maskedQuoteId, + string $shippingMethodCode, + string $shippingCarrierCode, + string $shippingAddressId + ) : string { + return <<<QUERY +mutation { + setShippingMethodsOnCart(input: + { + cart_id: "$maskedQuoteId", + shipping_addresses: [{ + cart_address_id: $shippingAddressId + shipping_method: { + method_code: "$shippingMethodCode" + carrier_code: "$shippingCarrierCode" + } + }] + }) { + + cart { + cart_id, + shipping_addresses { + selected_shipping_method { + carrier_code + method_code + label + amount + } + } + } + } +} + +QUERY; + } + + /** + * @param string $reversedQuoteId + * @return string + */ + private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string + { + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); + + return $this->quoteIdToMaskedId->execute((int)$quote->getId()); + } + + /** + * @param string $reversedQuoteId + * @param int $customerId + * @return string + */ + private function assignQuoteToCustomer( + string $reversedQuoteId, + int $customerId + ): string { + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); + $quote->setCustomerId($customerId); + $this->quoteResource->save($quote); + return $this->quoteIdToMaskedId->execute((int)$quote->getId()); + } + + /** + * @param string $username + * @param string $password + * @return array + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php new file mode 100644 index 0000000000000..78bf9f1cfc58b --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php @@ -0,0 +1,160 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for setting shipping methods on cart for guest + */ +class SetShippingMethodsOnCartTest extends GraphQlAbstract +{ + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var QuoteFactory + */ + private $quoteFactory; + + /** + * @var QuoteIdToMaskedQuoteIdInterface + */ + private $quoteIdToMaskedId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->quoteResource = $objectManager->get(QuoteResource::class); + $this->quoteFactory = $objectManager->get(QuoteFactory::class); + $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + } + + public function testShippingMethodWithVirtualProduct() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + public function testShippingMethodWithSimpleProduct() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + public function testShippingMethodWithSimpleProductWithoutAddress() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + public function testSetShippingMethodWithMissedRequiredParameters() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + public function testSetNonExistentShippingMethod() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + public function testSetShippingMethodIfAddressIsNotBelongToCart() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + public function testSetShippingMethodToNonExistentCart() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + public function testSetShippingMethodToGuestCart() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + public function testSetShippingMethodToAnotherCustomerCart() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + public function testSetShippingMethodToNonExistentCartAddress() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + public function testSetShippingMethodToGuestCartAddress() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + public function testSetShippingMethodToAnotherCustomerCartAddress() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + + /** + * @param string $maskedQuoteId + * @param string $shippingMethodCode + * @param string $shippingCarrierCode + * @param string $shippingAddressId + * @return string + */ + private function prepareMutationQuery( + string $maskedQuoteId, + string $shippingMethodCode, + string $shippingCarrierCode, + string $shippingAddressId + ) : string { + return <<<QUERY +mutation { + setShippingMethodsOnCart(input: + { + cart_id: "$maskedQuoteId", + shipping_addresses: [{ + cart_address_id: $shippingAddressId + shipping_method: { + method_code: "$shippingMethodCode" + carrier_code: "$shippingCarrierCode" + } + }] + }) { + cart { + shipping_addresses { + selected_shipping_method { + carrier_code + method_code + label + amount + } + } + } + } +} + +QUERY; + } + + /** + * @param string $reversedQuoteId + * @return string + */ + private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string + { + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id'); + + return $this->quoteIdToMaskedId->execute((int)$quote->getId()); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php deleted file mode 100644 index 704672b29d0e3..0000000000000 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodsOnCartTest.php +++ /dev/null @@ -1,356 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\GraphQl\Quote; - -use Magento\Integration\Api\CustomerTokenServiceInterface; -use Magento\Quote\Model\Quote; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; -use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\TestCase\GraphQlAbstract; - -/** - * Test for setting shipping methods on cart - */ -class SetShippingMethodsOnCartTest extends GraphQlAbstract -{ - /** - * @var CustomerTokenServiceInterface - */ - private $customerTokenService; - - /** - * @var QuoteResource - */ - private $quoteResource; - - /** - * @var Quote - */ - private $quote; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; - - /** - * @inheritdoc - */ - protected function setUp() - { - $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->create(QuoteResource::class); - $this->quote = $objectManager->create(Quote::class); - $this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class); - $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); - } - - /** - * Test for general routine of setting a shipping method on shopping cart - * - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php - */ - public function testSetShippingMethodOnCart() - { - $shippingCarrierCode = 'flatrate'; - $shippingMethodCode = 'flatrate'; - $this->quoteResource->load( - $this->quote, - 'test_order_1', - 'reserved_order_id' - ); - $shippingAddress = $this->quote->getShippingAddress(); - $shippingAddressId = $shippingAddress->getId(); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - - $query = $this->prepareMutationQuery( - $maskedQuoteId, - $shippingMethodCode, - $shippingCarrierCode, - $shippingAddressId - ); - - $response = $this->sendRequestWithToken($query); - - self::assertArrayHasKey('setShippingMethodsOnCart', $response); - self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); - self::assertEquals($maskedQuoteId, $response['setShippingMethodsOnCart']['cart']['cart_id']); - $addressesInformation = $response['setShippingMethodsOnCart']['cart']['shipping_addresses']; - self::assertCount(1, $addressesInformation); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php - */ - public function testSetFlatrateOnCart() - { - $this->setShippingMethodAndCheckResponse( - 'flatrate', - 'flatrate', - '10', - 'Flat Rate - Fixed' - ); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/OfflineShipping/_files/tablerates_weight.php - * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php - */ - public function testSetTableRatesOnCart() - { - $this->setShippingMethodAndCheckResponse( - 'tablerate', - 'bestway', - '10', - 'Best Way - Table Rate' - ); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php - */ - public function testSetFreeShippingOnCart() - { - $this->setShippingMethodAndCheckResponse( - 'freeshipping', - 'freeshipping', - '0', - 'Free Shipping - Free' - ); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php - */ - public function testSetUpsOnCart() - { - $this->setShippingMethodAndCheckResponse( - 'ups', - 'GND', - '15.61', - 'United Parcel Service - Ground' - ); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php - */ - public function testSetShippingMethodWithWrongCartId() - { - $shippingCarrierCode = 'flatrate'; - $shippingMethodCode = 'flatrate'; - $shippingAddressId = '1'; - $maskedQuoteId = 'invalid'; - - $query = $this->prepareMutationQuery( - $maskedQuoteId, - $shippingMethodCode, - $shippingCarrierCode, - $shippingAddressId - ); - - self::expectExceptionMessage("Could not find a cart with ID \"$maskedQuoteId\""); - $this->sendRequestWithToken($query); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php - */ - public function testSetNonExistingShippingMethod() - { - $shippingCarrierCode = 'non'; - $shippingMethodCode = 'existing'; - $this->quoteResource->load( - $this->quote, - 'test_order_1', - 'reserved_order_id' - ); - $shippingAddress = $this->quote->getShippingAddress(); - $shippingAddressId = $shippingAddress->getId(); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - - $query = $this->prepareMutationQuery( - $maskedQuoteId, - $shippingMethodCode, - $shippingCarrierCode, - $shippingAddressId - ); - - self::expectExceptionMessage("Carrier with such method not found: $shippingCarrierCode, $shippingMethodCode"); - $this->sendRequestWithToken($query); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php - */ - public function testSetShippingMethodWithNonExistingAddress() - { - $shippingCarrierCode = 'flatrate'; - $shippingMethodCode = 'flatrate'; - $this->quoteResource->load( - $this->quote, - 'test_order_1', - 'reserved_order_id' - ); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - $shippingAddressId = '-20'; - - $query = $this->prepareMutationQuery( - $maskedQuoteId, - $shippingMethodCode, - $shippingCarrierCode, - $shippingAddressId - ); - - self::expectExceptionMessage('The shipping address is missing. Set the address and try again.'); - $this->sendRequestWithToken($query); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/Checkout/_files/enable_all_shipping_methods.php - */ - public function testSetShippingMethodByGuestToCustomerCart() - { - $shippingCarrierCode = 'flatrate'; - $shippingMethodCode = 'flatrate'; - $this->quoteResource->load( - $this->quote, - 'test_order_1', - 'reserved_order_id' - ); - $shippingAddress = $this->quote->getShippingAddress(); - $shippingAddressId = $shippingAddress->getId(); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - - $query = $this->prepareMutationQuery( - $maskedQuoteId, - $shippingMethodCode, - $shippingCarrierCode, - $shippingAddressId - ); - - self::expectExceptionMessage( - "The current user cannot perform operations on cart \"$maskedQuoteId\"" - ); - - $this->graphQlQuery($query); - } - - /** - * Send request for setting the requested shipping method and check the output - * - * @param string $shippingCarrierCode - * @param string $shippingMethodCode - * @param string $shippingAmount - * @param string $shippingLabel - * @throws \Magento\Framework\Exception\AuthenticationException - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - private function setShippingMethodAndCheckResponse( - string $shippingCarrierCode, - string $shippingMethodCode, - string $shippingAmount, - string $shippingLabel - ) { - $this->quoteResource->load( - $this->quote, - 'test_order_1', - 'reserved_order_id' - ); - $shippingAddress = $this->quote->getShippingAddress(); - $shippingAddressId = $shippingAddress->getId(); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - - $query = $this->prepareMutationQuery( - $maskedQuoteId, - $shippingMethodCode, - $shippingCarrierCode, - $shippingAddressId - ); - - $response = $this->sendRequestWithToken($query); - - $addressesInformation = $response['setShippingMethodsOnCart']['cart']['shipping_addresses']; - self::assertEquals($addressesInformation[0]['selected_shipping_method']['carrier_code'], $shippingCarrierCode); - self::assertEquals($addressesInformation[0]['selected_shipping_method']['method_code'], $shippingMethodCode); - self::assertEquals($addressesInformation[0]['selected_shipping_method']['amount'], $shippingAmount); - self::assertEquals($addressesInformation[0]['selected_shipping_method']['label'], $shippingLabel); - } - - /** - * Generates query for setting the specified shipping method on cart - * - * @param string $maskedQuoteId - * @param string $shippingMethodCode - * @param string $shippingCarrierCode - * @param string $shippingAddressId - * @return string - */ - private function prepareMutationQuery( - string $maskedQuoteId, - string $shippingMethodCode, - string $shippingCarrierCode, - string $shippingAddressId - ) : string { - return <<<QUERY -mutation { - setShippingMethodsOnCart(input: - { - cart_id: "$maskedQuoteId", - shipping_addresses: [{ - cart_address_id: $shippingAddressId - shipping_method: { - method_code: "$shippingMethodCode" - carrier_code: "$shippingCarrierCode" - } - }] - }) { - - cart { - cart_id, - shipping_addresses { - selected_shipping_method { - carrier_code - method_code - label - amount - } - } - } - } -} - -QUERY; - } - - /** - * Sends a GraphQL request with using a bearer token - * - * @param string $query - * @return array - * @throws \Magento\Framework\Exception\AuthenticationException - */ - private function sendRequestWithToken(string $query): array - { - - $customerToken = $this->customerTokenService->createCustomerAccessToken('customer@example.com', 'password'); - $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; - - return $this->graphQlQuery($query, [], '', $headerMap); - } -} From b424d79207b9a6d8cbdeed3255a9192b25c08b25 Mon Sep 17 00:00:00 2001 From: Valerii Naida <vnayda@adobe.com> Date: Mon, 4 Mar 2019 11:52:07 -0600 Subject: [PATCH 101/108] GraphQL-418: [Shipping methods] Set Shipping Methods on Cart --- .../Customer/SetShippingMethodsOnCartTest.php | 23 +++++++++++++------ .../Guest/SetShippingMethodsOnCartTest.php | 5 ++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php index 6fe899145f369..52707dfd6ca40 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php @@ -53,58 +53,67 @@ protected function setUp() public function testShippingMethodWithVirtualProduct() { - + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } public function testShippingMethodWithSimpleProduct() { - + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } public function testShippingMethodWithSimpleProductWithoutAddress() { - + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } public function testSetShippingMethodWithMissedRequiredParameters() { - + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } public function testSetNonExistentShippingMethod() { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } public function testSetShippingMethodIfAddressIsNotBelongToCart() { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } public function testSetShippingMethodToNonExistentCart() { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } public function testSetShippingMethodToGuestCart() { - + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } public function testSetShippingMethodToAnotherCustomerCart() { - + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } public function testSetShippingMethodToNonExistentCartAddress() { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } public function testSetShippingMethodToGuestCartAddress() { - + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } public function testSetShippingMethodToAnotherCustomerCartAddress() { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); + } + public function testSetMultipleShippingMethods() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php index 78bf9f1cfc58b..e82a5956162df 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php @@ -104,6 +104,11 @@ public function testSetShippingMethodToAnotherCustomerCartAddress() $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); } + public function testSetMultipleShippingMethods() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422'); + } + /** * @param string $maskedQuoteId * @param string $shippingMethodCode From a13639c281105815c8c539518877ebaff99725cf Mon Sep 17 00:00:00 2001 From: Valerii Naida <vnayda@adobe.com> Date: Mon, 4 Mar 2019 18:57:51 -0600 Subject: [PATCH 102/108] GraphQL-418: [Shipping methods] Set Shipping Methods on Cart --- .../ConfigurableProductGraphQl/etc/module.xml | 1 - .../Magento/QuoteGraphQl/etc/schema.graphqls | 16 ---------------- .../Customer/SetShippingMethodsOnCartTest.php | 4 ++++ .../Quote/Guest/SetShippingMethodsOnCartTest.php | 2 ++ .../_files/enable_all_shipping_methods.php | 5 +++++ .../enable_all_shipping_methods_rollback.php | 5 +++++ .../OfflineShipping/_files/tablerates_weight.php | 1 + .../_files/tablerates_weight_rollback.php | 1 + 8 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml index f249a417f1046..98e7957d0af8e 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml @@ -12,7 +12,6 @@ <module name="Magento_ConfigurableProduct"/> <module name="Magento_GraphQl"/> <module name="Magento_CatalogGraphQl"/> - <module name="Magento_QuoteGraphQl"/> </sequence> </module> </config> diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 7328a328ce2bf..b94406f33c664 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -101,10 +101,6 @@ input ShippingMethodInput { cart_address_id: Int! carrier_code: String! method_code: String! - additional_data: ShippingMethodAdditionalDataInput -} - -input ShippingMethodAdditionalDataInput { } input SetPaymentMethodOnCartInput { @@ -115,10 +111,6 @@ input SetPaymentMethodOnCartInput { input PaymentMethodInput { code: String! @doc(description:"Payment method code") purchase_order_number: String @doc(description:"Purchase order number") - additional_data: PaymentMethodAdditionalDataInput -} - -input PaymentMethodAdditionalDataInput { } type SetPaymentMethodOnCartOutput { @@ -189,10 +181,6 @@ type SelectedShippingMethod { method_code: String label: String amount: Float - additional_data: SelectedShippingMethodAdditionalData -} - -type SelectedShippingMethodAdditionalData { } type AvailableShippingMethod { @@ -215,10 +203,6 @@ type AvailablePaymentMethod { type SelectedPaymentMethod { code: String @doc(description: "The payment method code") purchase_order_number: String @doc(description: "The purchase order number.") - additional_data: SelectedPaymentMethodAdditionalData -} - -type SelectedPaymentMethodAdditionalData { } enum AdressTypeEnum { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php index 52707dfd6ca40..736ea69440753 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php @@ -122,6 +122,7 @@ public function testSetMultipleShippingMethods() * @param string $shippingCarrierCode * @param string $shippingAddressId * @return string + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function prepareMutationQuery( string $maskedQuoteId, @@ -163,6 +164,7 @@ private function prepareMutationQuery( /** * @param string $reversedQuoteId * @return string + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string { @@ -176,6 +178,7 @@ private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): str * @param string $reversedQuoteId * @param int $customerId * @return string + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function assignQuoteToCustomer( string $reversedQuoteId, @@ -192,6 +195,7 @@ private function assignQuoteToCustomer( * @param string $username * @param string $password * @return array + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php index e82a5956162df..f159cb6f6151e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php @@ -115,6 +115,7 @@ public function testSetMultipleShippingMethods() * @param string $shippingCarrierCode * @param string $shippingAddressId * @return string + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function prepareMutationQuery( string $maskedQuoteId, @@ -154,6 +155,7 @@ private function prepareMutationQuery( /** * @param string $reversedQuoteId * @return string + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string { diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods.php index c0ee7f407caf2..dd48975aa2b09 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods.php @@ -1,4 +1,9 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); use Magento\Framework\App\Config\Storage\Writer; use Magento\Framework\App\Config\Storage\WriterInterface; diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods_rollback.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods_rollback.php index 2071e84e53893..7a3ca79febf6d 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/enable_all_shipping_methods_rollback.php @@ -1,4 +1,9 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); use Magento\Framework\App\Config\Storage\Writer; use Magento\Framework\App\Config\Storage\WriterInterface; diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight.php b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight.php index 99857304f6239..40e01a81ac807 100644 --- a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight.php +++ b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class); diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight_rollback.php b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight_rollback.php index 6401407d35543..cb6e9353b8972 100644 --- a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight_rollback.php +++ b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_weight_rollback.php @@ -3,5 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); require 'tablerates_rollback.php'; From bfacd22142a3da81f47438bba1a1879f28d71fc6 Mon Sep 17 00:00:00 2001 From: Valerii Naida <vnayda@adobe.com> Date: Mon, 4 Mar 2019 20:40:18 -0600 Subject: [PATCH 103/108] GraphQL-418: [Shipping methods] Set Shipping Methods on Cart --- .../Resolver/ShippingAdress/AvailableShippingMethods.php | 2 +- .../Resolver/{ => ShippingAdress}/SelectedShippingMethod.php | 2 +- app/code/Magento/QuoteGraphQl/etc/schema.graphqls | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename app/code/Magento/QuoteGraphQl/Model/Resolver/{ => ShippingAdress}/SelectedShippingMethod.php (95%) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/AvailableShippingMethods.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/AvailableShippingMethods.php index 7804b8defe378..a9e0ba59d15d9 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/AvailableShippingMethods.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/AvailableShippingMethods.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\QuoteGraphQl\Model\Resolver\ShippingAdress; +namespace Magento\QuoteGraphQl\Model\Resolver\ShippingAddress; use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Framework\Exception\LocalizedException; diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedShippingMethod.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/SelectedShippingMethod.php similarity index 95% rename from app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedShippingMethod.php rename to app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/SelectedShippingMethod.php index 06e9594fe2388..c58affa064c89 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedShippingMethod.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/SelectedShippingMethod.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\QuoteGraphQl\Model\Resolver; +namespace Magento\QuoteGraphQl\Model\Resolver\ShippingAddress; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Config\Element\Field; diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index b94406f33c664..5f65a990bac21 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -154,8 +154,8 @@ type CartAddress { country: CartAddressCountry telephone: String address_type: AdressTypeEnum - available_shipping_methods: [AvailableShippingMethod] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAdress\\AvailableShippingMethods") - selected_shipping_method: SelectedShippingMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAdress\\SelectedShippingMethod") + available_shipping_methods: [AvailableShippingMethod] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddress\\AvailableShippingMethods") + selected_shipping_method: SelectedShippingMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddress\\SelectedShippingMethod") items_weight: Float customer_notes: String cart_items: [CartItemQuantity] From 4e0503584d8eeb99ac3b2d87fde72a5394992c67 Mon Sep 17 00:00:00 2001 From: Ievgenii Gryshkun <i.gryshkun@gmail.com> Date: Tue, 5 Mar 2019 15:48:01 +0200 Subject: [PATCH 104/108] address_type is null after setting billing and shipping addresses on cart --- app/code/Magento/QuoteGraphQl/etc/schema.graphqls | 5 +++++ .../Quote/Customer/SetBillingAddressOnCartTest.php | 13 +++++++++---- .../Quote/Guest/SetBillingAddressOnCartTest.php | 13 +++++++++---- .../GraphQl/Quote/SetShippingAddressOnCartTest.php | 3 +++ 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index bcfead583d3af..30acac7ab49d9 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -211,6 +211,11 @@ type SelectedPaymentMethod { type SelectedPaymentMethodAdditionalData { } +enum AdressTypeEnum { + SHIPPING + BILLING +} + type AppliedCoupon { code: String! } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php index 2e0b57f96fe3a..8a0dcf1483b1c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php @@ -90,6 +90,7 @@ public function testSetNewBillingAddress() code label } + address_type } } } @@ -147,6 +148,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } + address_type } shipping_addresses { firstname @@ -160,6 +162,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } + address_type } } } @@ -174,7 +177,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() self::assertArrayHasKey('shipping_addresses', $cartResponse); $shippingAddressResponse = current($cartResponse['shipping_addresses']); $this->assertNewAddressFields($billingAddressResponse); - $this->assertNewAddressFields($shippingAddressResponse); + $this->assertNewAddressFields($shippingAddressResponse, 'shipping'); } /** @@ -403,9 +406,10 @@ public function testSetBillingAddressIfCustomerIsNotOwnerOfAddress() /** * Verify the all the whitelisted fields for a New Address Object * - * @param array $billingAddressResponse + * @param array $addressResponse + * @param string $addressType */ - private function assertNewAddressFields(array $billingAddressResponse): void + private function assertNewAddressFields(array $addressResponse, string $addressType = 'billing'): void { $assertionMap = [ ['response_field' => 'firstname', 'expected_value' => 'test firstname'], @@ -416,9 +420,10 @@ private function assertNewAddressFields(array $billingAddressResponse): void ['response_field' => 'postcode', 'expected_value' => '887766'], ['response_field' => 'telephone', 'expected_value' => '88776655'], ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], + ['response_field' => 'address_type', 'expected_value' => $addressType] ]; - $this->assertResponseFields($billingAddressResponse, $assertionMap); + $this->assertResponseFields($addressResponse, $assertionMap); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php index 24fc8353d2552..5fb53e699bc1e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php @@ -82,6 +82,7 @@ public function testSetNewBillingAddress() code label } + address_type } } } @@ -138,6 +139,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } + address_type } shipping_addresses { firstname @@ -151,6 +153,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } + address_type } } } @@ -165,7 +168,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() self::assertArrayHasKey('shipping_addresses', $cartResponse); $shippingAddressResponse = current($cartResponse['shipping_addresses']); $this->assertNewAddressFields($billingAddressResponse); - $this->assertNewAddressFields($shippingAddressResponse); + $this->assertNewAddressFields($shippingAddressResponse, 'shipping'); } /** @@ -244,9 +247,10 @@ public function testSetBillingAddressFromAddressBook() /** * Verify the all the whitelisted fields for a New Address Object * - * @param array $billingAddressResponse + * @param array $addressResponse + * @param string $addressType */ - private function assertNewAddressFields(array $billingAddressResponse): void + private function assertNewAddressFields(array $addressResponse, string $addressType = 'billing'): void { $assertionMap = [ ['response_field' => 'firstname', 'expected_value' => 'test firstname'], @@ -257,9 +261,10 @@ private function assertNewAddressFields(array $billingAddressResponse): void ['response_field' => 'postcode', 'expected_value' => '887766'], ['response_field' => 'telephone', 'expected_value' => '88776655'], ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], + ['response_field' => 'address_type', 'expected_value' => $addressType] ]; - $this->assertResponseFields($billingAddressResponse, $assertionMap); + $this->assertResponseFields($addressResponse, $assertionMap); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingAddressOnCartTest.php index 1f9aca2f12013..a27205a2455ef 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingAddressOnCartTest.php @@ -94,6 +94,7 @@ public function testSetNewShippingAddressByGuest() code label } + address_type } } } @@ -184,6 +185,7 @@ public function testSetNewShippingAddressByRegisteredCustomer() label code } + address_type } } } @@ -472,6 +474,7 @@ private function assertNewShippingAddressFields(array $shippingAddressResponse): ['response_field' => 'postcode', 'expected_value' => '887766'], ['response_field' => 'telephone', 'expected_value' => '88776655'], ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], + ['response_field' => 'address_type', 'expected_value' => 'shipping'] ]; $this->assertResponseFields($shippingAddressResponse, $assertionMap); From 031dc636e3d8e851e26264dc33f5f6a354d79bcd Mon Sep 17 00:00:00 2001 From: Valerii Naida <vnayda@adobe.com> Date: Tue, 5 Mar 2019 10:39:46 -0600 Subject: [PATCH 105/108] GraphQL-418: [Shipping methods] Set Shipping Methods on Cart --- .../AvailableShippingMethods.php | 0 .../SelectedShippingMethod.php | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename app/code/Magento/QuoteGraphQl/Model/Resolver/{ShippingAdress => ShippingAddress}/AvailableShippingMethods.php (100%) rename app/code/Magento/QuoteGraphQl/Model/Resolver/{ShippingAdress => ShippingAddress}/SelectedShippingMethod.php (100%) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/AvailableShippingMethods.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php similarity index 100% rename from app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/AvailableShippingMethods.php rename to app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/SelectedShippingMethod.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php similarity index 100% rename from app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/SelectedShippingMethod.php rename to app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php From 6f9288ff8a249a491a823e329b2b8c80fd785247 Mon Sep 17 00:00:00 2001 From: Valerii Naida <vnayda@adobe.com> Date: Tue, 5 Mar 2019 12:16:22 -0600 Subject: [PATCH 106/108] GraphQL-409: address_type is null after setting billing and shipping addresses on cart --- app/code/Magento/QuoteGraphQl/etc/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 30acac7ab49d9..aa4c25a513f67 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -158,7 +158,7 @@ type CartAddress { postcode: String country: CartAddressCountry telephone: String - address_type: String + address_type: AdressTypeEnum available_shipping_methods: [AvailableShippingMethod] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAdress\\AvailableShippingMethods") selected_shipping_method: SelectedShippingMethod items_weight: Float From d09e3b7dd3a0fb80e5776882d07ff83cf0c93502 Mon Sep 17 00:00:00 2001 From: Valerii Naida <vnayda@adobe.com> Date: Tue, 5 Mar 2019 13:53:40 -0600 Subject: [PATCH 107/108] GraphQL-409: address_type is null after setting billing and shipping addresses on cart --- .../Model/Cart/ExtractDataFromAddress.php | 11 ++++++++++- .../Quote/Customer/SetBillingAddressOnCartTest.php | 4 ++-- .../Quote/Guest/SetBillingAddressOnCartTest.php | 4 ++-- .../GraphQl/Quote/SetShippingAddressOnCartTest.php | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php index 57d8ec0bffdc3..20212d3412595 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php @@ -7,6 +7,7 @@ namespace Magento\QuoteGraphQl\Model\Cart; +use Magento\Customer\Model\Address\AbstractAddress; use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Quote\Api\Data\AddressInterface; use Magento\Quote\Model\Quote\Address as QuoteAddress; @@ -40,9 +41,17 @@ public function execute(QuoteAddress $address): array $addressData = $this->dataObjectConverter->toFlatArray($address, [], AddressInterface::class); $addressData['model'] = $address; + if ($address->getAddressType() == AbstractAddress::TYPE_SHIPPING) { + $addressType = 'SHIPPING'; + } elseif ($address->getAddressType() == AbstractAddress::TYPE_BILLING) { + $addressType = 'BILLING'; + } else { + $addressType = null; + } + $addressData = array_merge($addressData, [ 'address_id' => $address->getId(), - 'address_type' => $address->getAddressType(), + 'address_type' => $addressType, 'country' => [ 'code' => $address->getCountryId(), 'label' => $address->getCountry() diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php index 29add5f66d208..1a93c011e80a8 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php @@ -177,7 +177,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() self::assertArrayHasKey('shipping_addresses', $cartResponse); $shippingAddressResponse = current($cartResponse['shipping_addresses']); $this->assertNewAddressFields($billingAddressResponse); - $this->assertNewAddressFields($shippingAddressResponse, 'shipping'); + $this->assertNewAddressFields($shippingAddressResponse, 'SHIPPING'); } /** @@ -409,7 +409,7 @@ public function testSetBillingAddressIfCustomerIsNotOwnerOfAddress() * @param array $addressResponse * @param string $addressType */ - private function assertNewAddressFields(array $addressResponse, string $addressType = 'billing'): void + private function assertNewAddressFields(array $addressResponse, string $addressType = 'BILLING'): void { $assertionMap = [ ['response_field' => 'firstname', 'expected_value' => 'test firstname'], diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php index 5fb53e699bc1e..880d6aa0f406f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php @@ -168,7 +168,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() self::assertArrayHasKey('shipping_addresses', $cartResponse); $shippingAddressResponse = current($cartResponse['shipping_addresses']); $this->assertNewAddressFields($billingAddressResponse); - $this->assertNewAddressFields($shippingAddressResponse, 'shipping'); + $this->assertNewAddressFields($shippingAddressResponse, 'SHIPPING'); } /** @@ -250,7 +250,7 @@ public function testSetBillingAddressFromAddressBook() * @param array $addressResponse * @param string $addressType */ - private function assertNewAddressFields(array $addressResponse, string $addressType = 'billing'): void + private function assertNewAddressFields(array $addressResponse, string $addressType = 'BILLING'): void { $assertionMap = [ ['response_field' => 'firstname', 'expected_value' => 'test firstname'], diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingAddressOnCartTest.php index a27205a2455ef..4916bb2aa78c3 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingAddressOnCartTest.php @@ -474,7 +474,7 @@ private function assertNewShippingAddressFields(array $shippingAddressResponse): ['response_field' => 'postcode', 'expected_value' => '887766'], ['response_field' => 'telephone', 'expected_value' => '88776655'], ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], - ['response_field' => 'address_type', 'expected_value' => 'shipping'] + ['response_field' => 'address_type', 'expected_value' => 'SHIPPING'] ]; $this->assertResponseFields($shippingAddressResponse, $assertionMap); From abb7a6befa149877d27ae9344c1fbacbc07dc2ff Mon Sep 17 00:00:00 2001 From: Viktor Tymchynskyi <vtymchynskyi@magento.com> Date: Tue, 5 Mar 2019 17:25:25 -0600 Subject: [PATCH 108/108] MAGETWO-95425: sortOrder attribute for overrided Array-type arguments in Di-configuration doesn't apply - Add to blacklist --- .../Magento/Test/Php/_files/phpcpd/blacklist/common.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt index 0450ae1330cd8..c8f363dfbb716 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt @@ -209,3 +209,8 @@ Magento/Customer/Model/FileUploaderDataResolver.php Magento/Customer/Model/Customer/DataProvider.php Magento/InventoryShippingAdminUi/Ui/DataProvider Magento/Elasticsearch6/Model/Client +Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection +Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection +Magento/Elasticsearch/Model/Layer/Category/ItemCollectionProvider +Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider +Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/CompositeResolver