diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index 6d9f5e33dd55b..4e3a8403f3132 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -7,6 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver; +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Exception\GraphQlInputException; @@ -27,16 +28,26 @@ class CategoryTree implements ResolverInterface */ private $categoryTree; + /** + * @var ExtractDataFromCategoryTree + */ + private $extractDataFromCategoryTree; + /** * @param \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree + * @param ExtractDataFromCategoryTree $extractDataFromCategoryTree */ public function __construct( - \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree + \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree, + ExtractDataFromCategoryTree $extractDataFromCategoryTree ) { $this->categoryTree = $categoryTree; + $this->extractDataFromCategoryTree = $extractDataFromCategoryTree; } /** + * Get category id + * * @param array $args * @return int * @throws GraphQlInputException @@ -62,7 +73,8 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $rootCategoryId = $this->getCategoryId($args); $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId); if (!empty($categoriesTree)) { - return current($categoriesTree); + $result = $this->extractDataFromCategoryTree->execute($categoriesTree); + return current($result); } else { return null; } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index 07d8996838e9f..f2634574a2d15 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -9,7 +9,6 @@ use GraphQL\Language\AST\FieldNode; use Magento\CatalogGraphQl\Model\Category\DepthCalculator; -use Magento\CatalogGraphQl\Model\Category\Hydrator; use Magento\CatalogGraphQl\Model\Category\LevelCalculator; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; @@ -17,6 +16,7 @@ use Magento\Catalog\Model\ResourceModel\Category\Collection; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; use Magento\CatalogGraphQl\Model\AttributesJoiner; +use Magento\Catalog\Model\Category; /** * Category tree data provider @@ -53,33 +53,25 @@ class CategoryTree */ private $metadata; - /** - * @var Hydrator - */ - private $hydrator; - /** * @param CollectionFactory $collectionFactory * @param AttributesJoiner $attributesJoiner * @param DepthCalculator $depthCalculator * @param LevelCalculator $levelCalculator * @param MetadataPool $metadata - * @param Hydrator $hydrator */ public function __construct( CollectionFactory $collectionFactory, AttributesJoiner $attributesJoiner, DepthCalculator $depthCalculator, LevelCalculator $levelCalculator, - MetadataPool $metadata, - Hydrator $hydrator + MetadataPool $metadata ) { $this->collectionFactory = $collectionFactory; $this->attributesJoiner = $attributesJoiner; $this->depthCalculator = $depthCalculator; $this->levelCalculator = $levelCalculator; $this->metadata = $metadata; - $this->hydrator = $hydrator; } /** @@ -87,17 +79,26 @@ public function __construct( * * @param ResolveInfo $resolveInfo * @param int $rootCategoryId - * @return array + * @return \Iterator */ - public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId) : array + public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId): \Iterator { $categoryQuery = $resolveInfo->fieldNodes[0]; $collection = $this->collectionFactory->create(); $this->joinAttributesRecursively($collection, $categoryQuery); $depth = $this->depthCalculator->calculate($categoryQuery); $level = $this->levelCalculator->calculate($rootCategoryId); + + // If root category is being filter, we've to remove first slash + if ($rootCategoryId == Category::TREE_ROOT_ID) { + $regExpPathFilter = sprintf('.*%s/[/0-9]*$', $rootCategoryId); + } else { + $regExpPathFilter = sprintf('.*/%s/[/0-9]*$', $rootCategoryId); + } + //Search for desired part of category tree - $collection->addPathFilter(sprintf('.*/%s/[/0-9]*$', $rootCategoryId)); + $collection->addPathFilter($regExpPathFilter); + $collection->addFieldToFilter('level', ['gt' => $level]); $collection->addFieldToFilter('level', ['lteq' => $level + $depth - self::DEPTH_OFFSET]); $collection->setOrder('level'); @@ -105,35 +106,11 @@ public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId) : array $this->metadata->getMetadata(CategoryInterface::class)->getIdentifierField() . ' = ?', $rootCategoryId ); - return $this->processTree($collection->getIterator()); - } - - /** - * Iterates through category tree - * - * @param \Iterator $iterator - * @return array - */ - private function processTree(\Iterator $iterator) : array - { - $tree = []; - while ($iterator->valid()) { - /** @var CategoryInterface $category */ - $category = $iterator->current(); - $iterator->next(); - $nextCategory = $iterator->current(); - $tree[$category->getId()] = $this->hydrator->hydrateCategory($category); - $tree[$category->getId()]['model'] = $category; - if ($nextCategory && (int) $nextCategory->getLevel() !== (int) $category->getLevel()) { - $tree[$category->getId()]['children'] = $this->processTree($iterator); - } - } - - return $tree; + return $collection->getIterator(); } /** - * Joins EAV attributes recursively + * Join attributes recursively * * @param Collection $collection * @param FieldNode $fieldNode diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php new file mode 100644 index 0000000000000..ac8d5709c85b3 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php @@ -0,0 +1,55 @@ +categoryHydrator = $categoryHydrator; + } + + /** + * Extract data from category tree + * + * @param \Iterator $iterator + * @return array + */ + public function execute(\Iterator $iterator): array + { + $tree = []; + while ($iterator->valid()) { + /** @var CategoryInterface $category */ + $category = $iterator->current(); + $iterator->next(); + $nextCategory = $iterator->current(); + $tree[$category->getId()] = $this->categoryHydrator->hydrateCategory($category); + $tree[$category->getId()]['model'] = $category; + if ($nextCategory && (int) $nextCategory->getLevel() !== (int) $category->getLevel()) { + $tree[$category->getId()]['children'] = $this->execute($iterator); + } + } + + return $tree; + } +} diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/module.xml b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/module.xml index be4bb9fcd7010..e6e0f6cf72100 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/module.xml +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/module.xml @@ -6,5 +6,10 @@ */ --> - + + + + + + diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls index b96cfcb03d41f..f4ad2e930ddab 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls @@ -4,6 +4,7 @@ interface ProductInterface { url_key: String @doc(description: "The part of the URL that identifies the product") url_path: String @doc(description: "The part of the URL that precedes the url_key") + url_rewrites: [UrlRewrite] @doc(description: "URL rewrites list") @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\UrlRewrite") } input ProductFilterInput { diff --git a/app/code/Magento/CmsGraphQl/composer.json b/app/code/Magento/CmsGraphQl/composer.json index eb515c91bbb38..6a2e3950f93d0 100644 --- a/app/code/Magento/CmsGraphQl/composer.json +++ b/app/code/Magento/CmsGraphQl/composer.json @@ -8,6 +8,10 @@ "magento/module-cms": "*", "magento/module-widget": "*" }, + "suggest": { + "magento/module-graph-ql": "*", + "magento/module-store-graph-ql": "*" + }, "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CmsGraphQl/etc/graphql/di.xml b/app/code/Magento/CmsGraphQl/etc/graphql/di.xml new file mode 100644 index 0000000000000..78c1071d8e07c --- /dev/null +++ b/app/code/Magento/CmsGraphQl/etc/graphql/di.xml @@ -0,0 +1,21 @@ + + + + + + + web/default/front + web/default/cms_home_page + web/default/no_route + web/default/cms_no_route + web/default/cms_no_cookies + web/default/show_cms_breadcrumbs + + + + diff --git a/app/code/Magento/CmsGraphQl/etc/schema.graphqls b/app/code/Magento/CmsGraphQl/etc/schema.graphqls index 997bbf920a09e..e8abd2201b886 100644 --- a/app/code/Magento/CmsGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CmsGraphQl/etc/schema.graphqls @@ -1,5 +1,14 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. +type StoreConfig @doc(description: "The type contains information about a store config") { + front : String @doc(description: "Default Web URL") + cms_home_page : String @doc(description: "CMS Home Page") + no_route : String @doc(description: "Default No-route URL") + cms_no_route : String @doc(description: "CMS No Route Page") + cms_no_cookies : String @doc(description: "CMS No Cookies Page") + show_cms_breadcrumbs : Int @doc(description: "Show Breadcrumbs for CMS Pages") +} + type Query { cmsPage ( diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/RevokeCustomerToken.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/RevokeCustomerToken.php new file mode 100644 index 0000000000000..7eb219a01217e --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/RevokeCustomerToken.php @@ -0,0 +1,67 @@ +userContext = $userContext; + $this->customerTokenService = $customerTokenService; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $customerId = (int)$this->userContext->getUserId(); + + if ($customerId === 0) { + throw new GraphQlAuthorizationException( + __( + 'Current customer does not have access to the resource "%1"', + [\Magento\Customer\Model\Customer::ENTITY] + ) + ); + } + + return $this->customerTokenService->revokeCustomerAccessToken($customerId); + } +} diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls index 93c741adea87b..f3e3365f1c35e 100644 --- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls @@ -8,6 +8,7 @@ type Query { type Mutation { generateCustomerToken(email: String!, password: String!): CustomerToken @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\Customer\\Account\\GenerateCustomerToken") @doc(description:"Retrieve Customer token") changeCustomerPassword(currentPassword: String!, newPassword: String!): Customer @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\Customer\\Account\\ChangePassword") @doc(description:"Changes password for logged in customer") + revokeCustomerToken: Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\Customer\\Account\\RevokeCustomerToken") @doc(description:"Revoke Customer token") } type CustomerToken { diff --git a/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php b/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php index 92926c12e86d3..8c2d6c36591d5 100644 --- a/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php +++ b/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php @@ -7,10 +7,10 @@ namespace Magento\StoreGraphQl\Model\Resolver\Store; -use Magento\Store\Api\Data\StoreConfigInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Store\Api\StoreConfigManagerInterface; -use Magento\Store\Api\StoreRepositoryInterface; -use Magento\Store\Api\StoreResolverInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; /** * StoreConfig field data provider, used for GraphQL request processing. @@ -23,39 +23,60 @@ class StoreConfigDataProvider private $storeConfigManager; /** - * @var StoreResolverInterface + * @var StoreManagerInterface */ - private $storeResolver; + private $storeManager; /** - * @var StoreRepositoryInterface + * @var ScopeConfigInterface */ - private $storeRepository; + private $scopeConfig; + + /** + * @var array + */ + private $extendedConfigData; /** * @param StoreConfigManagerInterface $storeConfigManager - * @param StoreResolverInterface $storeResolver - * @param StoreRepositoryInterface $storeRepository + * @param StoreManagerInterface $storeManager + * @param ScopeConfigInterface $scopeConfig + * @param array $extendedConfigData */ public function __construct( StoreConfigManagerInterface $storeConfigManager, - StoreResolverInterface $storeResolver, - StoreRepositoryInterface $storeRepository + StoreManagerInterface $storeManager, + ScopeConfigInterface $scopeConfig, + array $extendedConfigData = [] ) { $this->storeConfigManager = $storeConfigManager; - $this->storeResolver = $storeResolver; - $this->storeRepository = $storeRepository; + $this->storeManager = $storeManager; + $this->scopeConfig = $scopeConfig; + $this->extendedConfigData = $extendedConfigData; + } + + /** + * Get store config data + * + * @return array + */ + public function getStoreConfigData(): array + { + $storeConfigData = array_merge( + $this->getBaseConfigData(), + $this->getExtendedConfigData() + ); + return $storeConfigData; } /** - * Get store config for current store + * Get base config data * * @return array */ - public function getStoreConfig() : array + private function getBaseConfigData() : array { - $storeId = $this->storeResolver->getCurrentStoreId(); - $store = $this->storeRepository->getById($storeId); + $store = $this->storeManager->getStore(); $storeConfig = current($this->storeConfigManager->getStoreConfigs([$store->getCode()])); $storeConfigData = [ @@ -78,4 +99,23 @@ public function getStoreConfig() : array ]; return $storeConfigData; } + + /** + * Get extended config data + * + * @return array + */ + private function getExtendedConfigData() + { + $store = $this->storeManager->getStore(); + $extendedConfigData = []; + foreach ($this->extendedConfigData as $key => $path) { + $extendedConfigData[$key] = $this->scopeConfig->getValue( + $path, + ScopeInterface::SCOPE_STORE, + $store->getId() + ); + } + return $extendedConfigData; + } } diff --git a/app/code/Magento/StoreGraphQl/Model/Resolver/StoreConfigResolver.php b/app/code/Magento/StoreGraphQl/Model/Resolver/StoreConfigResolver.php index 39fcd1bf2792d..9c426172de85d 100644 --- a/app/code/Magento/StoreGraphQl/Model/Resolver/StoreConfigResolver.php +++ b/app/code/Magento/StoreGraphQl/Model/Resolver/StoreConfigResolver.php @@ -23,12 +23,12 @@ class StoreConfigResolver implements ResolverInterface private $storeConfigDataProvider; /** - * @param StoreConfigDataProvider $storeConfigDataProvider + * @param StoreConfigDataProvider $storeConfigsDataProvider */ public function __construct( - StoreConfigDataProvider $storeConfigDataProvider + StoreConfigDataProvider $storeConfigsDataProvider ) { - $this->storeConfigDataProvider = $storeConfigDataProvider; + $this->storeConfigDataProvider = $storeConfigsDataProvider; } /** @@ -41,8 +41,6 @@ public function resolve( array $value = null, array $args = null ) { - - $storeConfigData = $this->storeConfigDataProvider->getStoreConfig(); - return $storeConfigData; + return $this->storeConfigDataProvider->getStoreConfigData(); } } diff --git a/app/code/Magento/StoreGraphQl/composer.json b/app/code/Magento/StoreGraphQl/composer.json index d03d759babd22..d53ba9fbb0023 100644 --- a/app/code/Magento/StoreGraphQl/composer.json +++ b/app/code/Magento/StoreGraphQl/composer.json @@ -8,8 +8,7 @@ "magento/module-store": "*" }, "suggest": { - "magento/module-graph-ql": "*", - "magento/module-catalog-graph-ql": "*" + "magento/module-graph-ql": "*" }, "license": [ "OSL-3.0", diff --git a/app/code/Magento/ThemeGraphQl/README.md b/app/code/Magento/ThemeGraphQl/README.md new file mode 100644 index 0000000000000..fed6b54fa5cf1 --- /dev/null +++ b/app/code/Magento/ThemeGraphQl/README.md @@ -0,0 +1,4 @@ +# ThemeGraphQlhQl + +**ThemeGraphQlhQl** provides type information for the GraphQl module +to generate theme fields information endpoints. diff --git a/app/code/Magento/ThemeGraphQl/composer.json b/app/code/Magento/ThemeGraphQl/composer.json new file mode 100644 index 0000000000000..e3aac55aea37e --- /dev/null +++ b/app/code/Magento/ThemeGraphQl/composer.json @@ -0,0 +1,24 @@ +{ + "name": "magento/module-theme-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0", + "magento/framework": "*" + }, + "suggest": { + "magento/module-store-graph-ql": "*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\ThemeGraphQl\\": "" + } + } +} diff --git a/app/code/Magento/ThemeGraphQl/etc/module.xml b/app/code/Magento/ThemeGraphQl/etc/module.xml new file mode 100644 index 0000000000000..0e10b776af9c0 --- /dev/null +++ b/app/code/Magento/ThemeGraphQl/etc/module.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/app/code/Magento/ThemeGraphQl/etc/schema.graphqls b/app/code/Magento/ThemeGraphQl/etc/schema.graphqls new file mode 100644 index 0000000000000..325fcc8bd9834 --- /dev/null +++ b/app/code/Magento/ThemeGraphQl/etc/schema.graphqls @@ -0,0 +1,19 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. +type StoreConfig @doc(description: "The type contains information about a store config") { + head_shortcut_icon : String @doc(description: "Favicon Icon") + default_title : String @doc(description: "Default Page Title") + title_prefix : String @doc(description: "Page Title Prefix") + title_suffix : String @doc(description: "Page Title Suffix") + default_description : String @doc(description: "Default Meta Description") + default_keywords : String @doc(description: "Default Meta Keywords") + head_includes : String @doc(description: "Scripts and Style Sheets") + demonotice : Int @doc(description: "Display Demo Store Notice") + header_logo_src : String @doc(description: "Logo Image") + logo_width : Int @doc(description: "Logo Attribute Width") + logo_height : Int @doc(description: "Logo Attribute Height") + welcome : String @doc(description: "Welcome Text") + logo_alt : String @doc(description: "Logo Image Alt") + absolute_footer : String @doc(description: "Footer Miscellaneous HTML") + copyright : String @doc(description: "Copyright") +} diff --git a/app/code/Magento/ThemeGraphQl/registration.php b/app/code/Magento/ThemeGraphQl/registration.php new file mode 100644 index 0000000000000..e320fbc9868ef --- /dev/null +++ b/app/code/Magento/ThemeGraphQl/registration.php @@ -0,0 +1,10 @@ +urlFinder = $urlFinder; + $this->storeManager = $storeManager; + $this->customUrlLocator = $customUrlLocator; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($args['url']) || empty(trim($args['url']))) { + throw new GraphQlInputException(__('"url" argument should be specified and not empty')); + } + + $result = null; + $url = $args['url']; + if (substr($url, 0, 1) === '/' && $url !== '/') { + $url = ltrim($url, '/'); + } + $customUrl = $this->customUrlLocator->locateUrl($url); + $url = $customUrl ?: $url; + $urlRewrite = $this->findCanonicalUrl($url); + if ($urlRewrite) { + $result = [ + 'id' => $urlRewrite->getEntityId(), + 'canonical_url' => $urlRewrite->getTargetPath(), + 'type' => $this->sanitizeType($urlRewrite->getEntityType()) + ]; + } + return $result; + } + + /** + * Find the canonical url passing through all redirects if any + * + * @param string $requestPath + * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null + */ + private function findCanonicalUrl(string $requestPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite + { + $urlRewrite = $this->findUrlFromRequestPath($requestPath); + if ($urlRewrite && $urlRewrite->getRedirectType() > 0) { + while ($urlRewrite && $urlRewrite->getRedirectType() > 0) { + $urlRewrite = $this->findUrlFromRequestPath($urlRewrite->getTargetPath()); + } + } + if (!$urlRewrite) { + $urlRewrite = $this->findUrlFromTargetPath($requestPath); + } + + return $urlRewrite; + } + + /** + * Find a url from a request url on the current store + * + * @param string $requestPath + * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null + */ + private function findUrlFromRequestPath(string $requestPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite + { + return $this->urlFinder->findOneByData( + [ + 'request_path' => $requestPath, + 'store_id' => $this->storeManager->getStore()->getId() + ] + ); + } + + /** + * Find a url from a target url on the current store + * + * @param string $targetPath + * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null + */ + private function findUrlFromTargetPath(string $targetPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite + { + return $this->urlFinder->findOneByData( + [ + 'target_path' => $targetPath, + 'store_id' => $this->storeManager->getStore()->getId() + ] + ); + } + + /** + * Sanitize the type to fit schema specifications + * + * @param string $type + * @return string + */ + private function sanitizeType(string $type) : string + { + return strtoupper(str_replace('-', '_', $type)); + } +} diff --git a/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/UrlRewrite.php b/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/UrlRewrite.php index 5bdaf36272536..0c4c78b582941 100644 --- a/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/UrlRewrite.php +++ b/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/UrlRewrite.php @@ -11,12 +11,12 @@ use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; -use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\Model\AbstractModel; use Magento\UrlRewrite\Model\UrlFinderInterface; -use Magento\UrlRewriteGraphQl\Model\Resolver\UrlRewrite\CustomUrlLocatorInterface; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite as UrlRewriteDTO; /** - * UrlRewrite field resolver, used for GraphQL request processing. + * Returns URL rewrites list for the specified product */ class UrlRewrite implements ResolverInterface { @@ -25,29 +25,13 @@ class UrlRewrite implements ResolverInterface */ private $urlFinder; - /** - * @var StoreManagerInterface - */ - private $storeManager; - - /** - * @var CustomUrlLocatorInterface - */ - private $customUrlLocator; - /** * @param UrlFinderInterface $urlFinder - * @param StoreManagerInterface $storeManager - * @param CustomUrlLocatorInterface $customUrlLocator */ public function __construct( - UrlFinderInterface $urlFinder, - StoreManagerInterface $storeManager, - CustomUrlLocatorInterface $customUrlLocator + UrlFinderInterface $urlFinder ) { $this->urlFinder = $urlFinder; - $this->storeManager = $storeManager; - $this->customUrlLocator = $customUrlLocator; } /** @@ -59,90 +43,51 @@ public function resolve( ResolveInfo $info, array $value = null, array $args = null - ) { - if (!isset($args['url']) || empty(trim($args['url']))) { - throw new GraphQlInputException(__('"url" argument should be specified and not empty')); + ): array { + if (!isset($value['model'])) { + throw new GraphQlInputException(__('"model" value should be specified')); } - $result = null; - $url = $args['url']; - if (substr($url, 0, 1) === '/' && $url !== '/') { - $url = ltrim($url, '/'); - } - $customUrl = $this->customUrlLocator->locateUrl($url); - $url = $customUrl ?: $url; - $urlRewrite = $this->findCanonicalUrl($url); - if ($urlRewrite) { - $result = [ - 'id' => $urlRewrite->getEntityId(), - 'relative_url' => $urlRewrite->getTargetPath(), - 'type' => $this->sanitizeType($urlRewrite->getEntityType()) - ]; - } - return $result; - } + /** @var AbstractModel $entity */ + $entity = $value['model']; + $entityId = $entity->getEntityId(); - /** - * Find the canonical url passing through all redirects if any - * - * @param string $requestPath - * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null - */ - private function findCanonicalUrl(string $requestPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite - { - $urlRewrite = $this->findUrlFromRequestPath($requestPath); - if ($urlRewrite && $urlRewrite->getRedirectType() > 0) { - while ($urlRewrite && $urlRewrite->getRedirectType() > 0) { - $urlRewrite = $this->findUrlFromRequestPath($urlRewrite->getTargetPath()); + $urlRewriteCollection = $this->urlFinder->findAllByData([UrlRewriteDTO::ENTITY_ID => $entityId]); + $urlRewrites = []; + + /** @var UrlRewriteDTO $urlRewrite */ + foreach ($urlRewriteCollection as $urlRewrite) { + if ($urlRewrite->getRedirectType() !== 0) { + continue; } + + $urlRewrites[] = [ + 'url' => $urlRewrite->getRequestPath(), + 'parameters' => $this->getUrlParameters($urlRewrite->getTargetPath()) + ]; } - if (!$urlRewrite) { - $urlRewrite = $this->findUrlFromTargetPath($requestPath); - } - - return $urlRewrite; - } - /** - * Find a url from a request url on the current store - * - * @param string $requestPath - * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null - */ - private function findUrlFromRequestPath(string $requestPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite - { - return $this->urlFinder->findOneByData( - [ - 'request_path' => $requestPath, - 'store_id' => $this->storeManager->getStore()->getId() - ] - ); + return $urlRewrites; } /** - * Find a url from a target url on the current store + * Parses target path and extracts parameters * * @param string $targetPath - * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null + * @return array */ - private function findUrlFromTargetPath(string $targetPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite + private function getUrlParameters(string $targetPath): array { - return $this->urlFinder->findOneByData( - [ - 'target_path' => $targetPath, - 'store_id' => $this->storeManager->getStore()->getId() - ] - ); - } + $urlParameters = []; + $targetPathParts = explode('/', trim($targetPath, '/')); - /** - * Sanitize the type to fit schema specifications - * - * @param string $type - * @return string - */ - private function sanitizeType(string $type) : string - { - return strtoupper(str_replace('-', '_', $type)); + for ($i = 3; ($i < sizeof($targetPathParts) - 1); $i += 2) { + $urlParameters[] = [ + 'name' => $targetPathParts[$i], + 'value' => $targetPathParts[$i + 1] + ]; + } + + return $urlParameters; } } diff --git a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls index e9a39617774c0..dae695c69a33c 100644 --- a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls @@ -1,15 +1,25 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. -type EntityUrl @doc(description: "EntityUrl is an output object containing the `id`, `relative_url`, and `type` attributes") { +type EntityUrl @doc(description: "EntityUrl is an output object containing the `id`, `canonical_url`, and `type` attributes") { id: Int @doc(description: "The ID assigned to the object associated with the specified url. This could be a product ID, category ID, or page ID.") - relative_url: String @doc(description: "The internal relative URL. If the specified url is a redirect, the query returns the redirected URL, not the original.") + canonical_url: String @doc(description: "The internal relative URL. If the specified url is a redirect, the query returns the redirected URL, not the original.") type: UrlRewriteEntityTypeEnum @doc(description: "One of PRODUCT, CATEGORY, or CMS_PAGE.") } type Query { - urlResolver(url: String!): EntityUrl @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\UrlRewrite") @doc(description: "The urlResolver query returns the relative URL for a specified product, category or CMS page") + urlResolver(url: String!): EntityUrl @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\EntityUrl") @doc(description: "The urlResolver query returns the relative URL for a specified product, category or CMS page") } enum UrlRewriteEntityTypeEnum { } + +type UrlRewrite @doc(description: "The object contains URL rewrite details") { + url: String @doc(description: "Request URL") + parameters: [HttpQueryParameter] @doc(description: "Request parameters") +} + +type HttpQueryParameter @doc(description: "The object details of target path parameters") { + name: String @doc(description: "Parameter name") + value: String @doc(description: "Parameter value") +} diff --git a/composer.json b/composer.json index e2a646275d98b..3f8f0a033c893 100644 --- a/composer.json +++ b/composer.json @@ -221,6 +221,7 @@ "magento/module-tax": "*", "magento/module-tax-import-export": "*", "magento/module-theme": "*", + "magento/module-theme-graph-ql": "*", "magento/module-translation": "*", "magento/module-ui": "*", "magento/module-ups": "*", diff --git a/composer.lock b/composer.lock index 2550f70f0be81..1d101c8aaaf15 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": "18982aa4d36bcfd22cf073dfb578efdb", + "content-hash": "d6640ddfd342feceaec44c406c056f57", "packages": [ { "name": "braintree/braintree_php", diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/UrlRewritesTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/UrlRewritesTest.php new file mode 100644 index 0000000000000..c39b32e4bfa4f --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/UrlRewritesTest.php @@ -0,0 +1,149 @@ +graphQlQuery($query); + + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $product = $productRepository->get('virtual-product', false, null, true); + + $urlFinder = ObjectManager::getInstance()->get(UrlFinderInterface::class); + + $rewritesCollection = $urlFinder->findAllByData([UrlRewriteDTO::ENTITY_ID => $product->getId()]); + + /* There should be only one rewrite */ + /** @var UrlRewriteDTO $urlRewrite */ + $urlRewrite = current($rewritesCollection); + + $this->assertArrayHasKey('url_rewrites', $response['products']['items'][0]); + $this->assertCount(1, $response['products']['items'][0]['url_rewrites']); + + $this->assertResponseFields( + $response['products']['items'][0]['url_rewrites'][0], + [ + "url" => $urlRewrite->getRequestPath(), + "parameters" => $this->getUrlParameters($urlRewrite->getTargetPath()) + ] + ); + } + + /** + * + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testProductWithOneCategoryAssigned() + { + $productSku = 'simple'; + $query + = <<graphQlQuery($query); + + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $product = $productRepository->get('simple', false, null, true); + + $urlFinder = ObjectManager::getInstance()->get(UrlFinderInterface::class); + + $rewritesCollection = $urlFinder->findAllByData([UrlRewriteDTO::ENTITY_ID => $product->getId()]); + $rewritesCount = count($rewritesCollection); + + $this->assertArrayHasKey('url_rewrites', $response['products']['items'][0]); + $this->assertCount($rewritesCount, $response['products']['items'][0]['url_rewrites']); + + for ($i = 0; $i < $rewritesCount; $i++) { + $urlRewrite = $rewritesCollection[$i]; + $this->assertResponseFields( + $response['products']['items'][0]['url_rewrites'][$i], + [ + "url" => $urlRewrite->getRequestPath(), + "parameters" => $this->getUrlParameters($urlRewrite->getTargetPath()) + ] + ); + } + } + + /** + * Parses target path and extracts parameters + * + * @param string $targetPath + * @return array + */ + private function getUrlParameters(string $targetPath): array + { + $urlParameters = []; + $targetPathParts = explode('/', trim($targetPath, '/')); + + for ($i = 3; ($i < sizeof($targetPathParts) - 1); $i += 2) { + $urlParameters[] = [ + 'name' => $targetPathParts[$i], + 'value' => $targetPathParts[$i + 1] + ]; + } + + return $urlParameters; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/ProductOnlyXLeftInStockTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/ProductOnlyXLeftInStockTest.php index 4e49bb63e49a9..3969c758f12db 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/ProductOnlyXLeftInStockTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/ProductOnlyXLeftInStockTest.php @@ -16,7 +16,6 @@ class ProductOnlyXLeftInStockTest extends GraphQlAbstract */ public function testQueryProductOnlyXLeftInStockDisabled() { - $this->cleanCache(); $productSku = 'simple'; $query = <<get(\Magento\Integration\Api\CustomerTokenServiceInterface::class); + $customerToken = $customerTokenService->createCustomerAccessToken($userName, $password); + + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + $response = $this->graphQlQuery($query, [], '', $headerMap); + $this->assertTrue($response['revokeCustomerToken']); + } + + /** + * Verify guest customers + */ + public function testRevokeCustomerTokenForGuestCustomer() + { + $query = <<expectException(\Exception::class); + $this->expectExceptionMessage( + 'GraphQL response contains errors: Current customer' . ' ' . + 'does not have access to the resource "customer"' + ); + $this->graphQlQuery($query, [], ''); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/StoreConfigResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/StoreConfigResolverTest.php index 4657a1e763ae1..a536022e31631 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/StoreConfigResolverTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/StoreConfigResolverTest.php @@ -25,6 +25,7 @@ class StoreConfigResolverTest extends GraphQlAbstract protected function setUp() { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/167'); $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php index 370121a1dad78..c70b1631e85cd 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php @@ -31,7 +31,7 @@ protected function setUp() } /** - * Tests if target_path(relative_url) is resolved for Product entity + * Tests if target_path(canonical_url) is resolved for Product entity * * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php */ @@ -60,7 +60,7 @@ public function testProductUrlResolver() urlResolver(url:"{$urlPath}") { id - relative_url + canonical_url type } } @@ -68,12 +68,12 @@ public function testProductUrlResolver() $response = $this->graphQlQuery($query); $this->assertArrayHasKey('urlResolver', $response); $this->assertEquals($product->getEntityId(), $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']); $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); } /** - * Tests the use case where relative_url is provided as resolver input in the Query + * Tests the use case where canonical_url is provided as resolver input in the Query * * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php */ @@ -104,7 +104,7 @@ public function testProductUrlWithCanonicalUrlInput() urlResolver(url:"{$canonicalPath}") { id - relative_url + canonical_url type } } @@ -112,7 +112,7 @@ public function testProductUrlWithCanonicalUrlInput() $response = $this->graphQlQuery($query); $this->assertArrayHasKey('urlResolver', $response); $this->assertEquals($product->getEntityId(), $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']); $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); } @@ -147,7 +147,7 @@ public function testCategoryUrlResolver() urlResolver(url:"{$urlPath2}") { id - relative_url + canonical_url type } } @@ -155,7 +155,7 @@ public function testCategoryUrlResolver() $response = $this->graphQlQuery($query); $this->assertArrayHasKey('urlResolver', $response); $this->assertEquals($categoryId, $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']); $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); } @@ -183,14 +183,14 @@ public function testCMSPageUrlResolver() urlResolver(url:"{$requestPath}") { id - relative_url + canonical_url type } } QUERY; $response = $this->graphQlQuery($query); $this->assertEquals($cmsPageId, $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']); $this->assertEquals(strtoupper(str_replace('-', '_', $expectedEntityType)), $response['urlResolver']['type']); } @@ -226,7 +226,7 @@ public function testProductUrlRewriteResolver() urlResolver(url:"{$urlPath}") { id - relative_url + canonical_url type } } @@ -234,7 +234,7 @@ public function testProductUrlRewriteResolver() $response = $this->graphQlQuery($query); $this->assertArrayHasKey('urlResolver', $response); $this->assertEquals($product->getEntityId(), $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']); $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); } @@ -266,7 +266,7 @@ public function testInvalidUrlResolverInput() urlResolver(url:"{$urlPath}") { id - relative_url + canonical_url type } } @@ -307,7 +307,7 @@ public function testCategoryUrlWithLeadingSlash() urlResolver(url:"/{$urlPath}") { id - relative_url + canonical_url type } } @@ -315,7 +315,7 @@ public function testCategoryUrlWithLeadingSlash() $response = $this->graphQlQuery($query); $this->assertArrayHasKey('urlResolver', $response); $this->assertEquals($categoryId, $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']); $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); } @@ -344,7 +344,7 @@ public function testResolveSlash() urlResolver(url:"/") { id - relative_url + canonical_url type } } @@ -352,7 +352,7 @@ public function testResolveSlash() $response = $this->graphQlQuery($query); $this->assertArrayHasKey('urlResolver', $response); $this->assertEquals($homePageId, $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']); $this->assertEquals('CMS_PAGE', $response['urlResolver']['type']); } }