diff --git a/app/code/Magento/Backend/Block/Template.php b/app/code/Magento/Backend/Block/Template.php index 1bcd3033035dc0bf2de89782ad8972b8236e0d96..45d3830793e116f8d7029b4ae5c87ca80a83f280 100644 --- a/app/code/Magento/Backend/Block/Template.php +++ b/app/code/Magento/Backend/Block/Template.php @@ -70,17 +70,12 @@ class Template extends \Magento\Framework\View\Element\Template * * @param string $moduleName Full module name * @return boolean + * @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function isOutputEnabled($moduleName = null) { - if ($moduleName === null) { - $moduleName = $this->getModuleName(); - } - - return !$this->_scopeConfig->isSetFlag( - 'advanced/modules_disable_output/' . $moduleName, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); + return true; } /** diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 813a097ea6bf3ca2e615f0848619409c61f2dd9f..fc6c8df551913f5ad34d9875bc7e0e9173424681 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -16,7 +16,7 @@ <tab id="advanced" translate="label" sortOrder="999999"> <label>Advanced</label> </tab> - <section id="advanced" translate="label" type="text" sortOrder="910" showInDefault="1" showInWebsite="1" showInStore="1"> + <section id="advanced" translate="label" type="text" sortOrder="910" showInDefault="0" showInWebsite="0" showInStore="0"> <label>Advanced</label> <tab>advanced</tab> <resource>Magento_Backend::advanced</resource> diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Options/AjaxTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Options/AjaxTest.php index 0c6f680df486a50c584008b9cee288366c5d0209..1ca01ca6724ce741b58278ffe2d42a8522aa574c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Options/AjaxTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Options/AjaxTest.php @@ -55,12 +55,6 @@ class AjaxTest extends \PHPUnit_Framework_TestCase ->getMock(); $eventManager->expects($this->exactly(2))->method('dispatch')->will($this->returnValue(true)); - $scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config::class) - ->setMethods(['getValue']) - ->disableOriginalConstructor()->getMock(); - $scopeConfig->expects($this->once())->method('getValue')->withAnyParameters() - ->will($this->returnValue(false)); - $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)->disableOriginalConstructor() ->setMethods(['setStoreId', 'load', 'getId', '__wakeup', '__sleep']) ->getMock(); @@ -93,8 +87,6 @@ class AjaxTest extends \PHPUnit_Framework_TestCase $this->context->expects($this->once())->method('getEventManager') ->will($this->returnValue($eventManager)); - $this->context->expects($this->once())->method('getScopeConfig') - ->will($this->returnValue($scopeConfig)); $this->context->expects($this->once())->method('getLayout') ->will($this->returnValue($layout)); $this->context->expects($this->once())->method('getRequest') diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php index 9c153fb920279c48ac2e36014a178b67de66eb3f..90d76e469b7abe4d0c2e253bd174f0ee8437dc00 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/Widget/NewWidgetTest.php @@ -63,7 +63,7 @@ class NewWidgetTest extends \PHPUnit_Framework_TestCase false, false ); - $this->scopeConfig = $this->getMock(\Magento\Framework\App\Config::class, ['getValue'], [], '', false, false); + $this->scopeConfig = $this->getMock(\Magento\Framework\App\Config::class, [], [], '', false, false); $this->cacheState = $this->getMock( \Magento\Framework\App\Cache\State::class, ['isEnabled'], @@ -188,8 +188,6 @@ class NewWidgetTest extends \PHPUnit_Framework_TestCase { $this->eventManager->expects($this->exactly(2))->method('dispatch') ->will($this->returnValue(true)); - $this->scopeConfig->expects($this->once())->method('getValue')->withAnyParameters() - ->willReturn(false); $this->cacheState->expects($this->atLeastOnce())->method('isEnabled')->withAnyParameters() ->willReturn(false); $this->catalogConfig->expects($this->once())->method('getProductAttributes') diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php index 349a4772633ed086905183a7cc826b26782967d6..de647a34619dfe0355b9ff79277e3fc3ec24d131 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php @@ -7,6 +7,9 @@ namespace Magento\CatalogUrlRewrite\Model\Category; use Magento\Catalog\Model\Category; use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGeneratorFactory; +use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; +use Magento\UrlRewrite\Model\MergeDataProviderFactory; +use Magento\Framework\App\ObjectManager; class ChildrenUrlRewriteGenerator { @@ -16,16 +19,25 @@ class ChildrenUrlRewriteGenerator /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGeneratorFactory */ protected $categoryUrlRewriteGeneratorFactory; + /** @var \Magento\UrlRewrite\Model\MergeDataProvider */ + private $mergeDataProviderPrototype; + /** * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGeneratorFactory $categoryUrlRewriteGeneratorFactory + * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory */ public function __construct( ChildrenCategoriesProvider $childrenCategoriesProvider, - CategoryUrlRewriteGeneratorFactory $categoryUrlRewriteGeneratorFactory + CategoryUrlRewriteGeneratorFactory $categoryUrlRewriteGeneratorFactory, + MergeDataProviderFactory $mergeDataProviderFactory = null ) { $this->childrenCategoriesProvider = $childrenCategoriesProvider; $this->categoryUrlRewriteGeneratorFactory = $categoryUrlRewriteGeneratorFactory; + if (!isset($mergeDataProviderFactory)) { + $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class); + } + $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); } /** @@ -33,19 +45,22 @@ class ChildrenUrlRewriteGenerator * * @param int $storeId * @param \Magento\Catalog\Model\Category $category + * @param int|null $rootCategoryId * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - public function generate($storeId, Category $category) + public function generate($storeId, Category $category, $rootCategoryId = null) { - $urls = []; - foreach ($this->childrenCategoriesProvider->getChildren($category) as $childCategory) { + $mergeDataProvider = clone $this->mergeDataProviderPrototype; + foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) { $childCategory->setStoreId($storeId); $childCategory->setData('save_rewrites_history', $category->getData('save_rewrites_history')); - $urls = array_merge( - $urls, - $this->categoryUrlRewriteGeneratorFactory->create()->generate($childCategory) + /** @var CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator */ + $categoryUrlRewriteGenerator = $this->categoryUrlRewriteGeneratorFactory->create(); + $mergeDataProvider->merge( + $categoryUrlRewriteGenerator->generate($childCategory, false, $rootCategoryId) ); } - return $urls; + + return $mergeDataProvider->getData(); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php index d4581744bcb07ec86b60e34e5c80ce0e46878d85..c994b424641470bf728765594460266f3c5311f2 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php @@ -5,13 +5,8 @@ */ namespace Magento\CatalogUrlRewrite\Model\Category; -use Magento\Catalog\Model\Category; -use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; use Magento\UrlRewrite\Model\OptionProvider; -use Magento\UrlRewrite\Model\UrlFinderInterface; -use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; -use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; class CurrentUrlRewritesRegenerator { @@ -21,25 +16,53 @@ class CurrentUrlRewritesRegenerator /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory */ protected $urlRewriteFactory; - /** @var UrlFinderInterface */ - protected $urlFinder; + /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite */ + private $urlRewritePrototype; - /** @var \Magento\Catalog\Model\Category */ + /** + * @var \Magento\Catalog\Model\Category + * @deprecated + */ protected $category; + /** + * @var \Magento\UrlRewrite\Model\UrlFinderInterface + * @deprecated + */ + protected $urlFinder; + + /** @var \Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder */ + private $urlRewriteFinder; + + /** @var \Magento\UrlRewrite\Model\MergeDataProvider */ + private $mergeDataProviderPrototype; + /** * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory $urlRewriteFactory - * @param UrlFinderInterface $urlFinder + * @param \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder + * @param \Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder|null $urlRewriteFinder + * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory */ public function __construct( - CategoryUrlPathGenerator $categoryUrlPathGenerator, - UrlRewriteFactory $urlRewriteFactory, - UrlFinderInterface $urlFinder + \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator, + \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory $urlRewriteFactory, + \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder, + \Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder $urlRewriteFinder = null, + \Magento\UrlRewrite\Model\MergeDataProviderFactory $mergeDataProviderFactory = null ) { $this->categoryUrlPathGenerator = $categoryUrlPathGenerator; $this->urlRewriteFactory = $urlRewriteFactory; + $this->urlRewritePrototype = $urlRewriteFactory->create(); $this->urlFinder = $urlFinder; + $this->urlRewriteFinder = $urlRewriteFinder ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder::class); + if (!isset($mergeDataProviderFactory)) { + $mergeDataProviderFactory = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\UrlRewrite\Model\MergeDataProviderFactory::class + ); + } + $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); } /** @@ -47,70 +70,70 @@ class CurrentUrlRewritesRegenerator * * @param int $storeId * @param \Magento\Catalog\Model\Category $category + * @param int|null $rootCategoryId * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - public function generate($storeId, Category $category) + public function generate($storeId, \Magento\Catalog\Model\Category $category, $rootCategoryId = null) { - $this->category = $category; - - $currentUrlRewrites = $this->urlFinder->findAllByData( - [ - UrlRewrite::STORE_ID => $storeId, - UrlRewrite::ENTITY_ID => $category->getId(), - UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE, - ] + $mergeDataProvider = clone $this->mergeDataProviderPrototype; + $currentUrlRewrites = $this->urlRewriteFinder->findAllByData( + $category->getEntityId(), + $storeId, + CategoryUrlRewriteGenerator::ENTITY_TYPE, + $rootCategoryId ); - $urlRewrites = []; foreach ($currentUrlRewrites as $rewrite) { - if ($rewrite->getIsAutogenerated()) { - $urlRewrites = array_merge($urlRewrites, $this->generateForAutogenerated($rewrite, $storeId)); - } else { - $urlRewrites = array_merge($urlRewrites, $this->generateForCustom($rewrite, $storeId)); - } + $mergeDataProvider->merge( + $rewrite->getIsAutogenerated() + ? $this->generateForAutogenerated($rewrite, $storeId, $category) + : $this->generateForCustom($rewrite, $storeId, $category) + ); } - return $urlRewrites; + + return $mergeDataProvider->getData(); } /** * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewrite $url * @param int $storeId - * @return array + * @param \Magento\Catalog\Model\Category|null $category + * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - protected function generateForAutogenerated($url, $storeId) + protected function generateForAutogenerated($url, $storeId, \Magento\Catalog\Model\Category $category = null) { - $urls = []; - if ($this->category->getData('save_rewrites_history')) { - $targetPath = $this->categoryUrlPathGenerator->getUrlPathWithSuffix($this->category, $storeId); + if ($category->getData('save_rewrites_history')) { + $targetPath = $this->categoryUrlPathGenerator->getUrlPathWithSuffix($category, $storeId); if ($url->getRequestPath() !== $targetPath) { - $urls[$url->getRequestPath() . '_' . $storeId] = $this->urlRewriteFactory->create() - ->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE) - ->setEntityId($this->category->getId()) + $generatedUrl = clone $this->urlRewritePrototype; + $generatedUrl->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE) + ->setEntityId($category->getEntityId()) ->setRequestPath($url->getRequestPath()) ->setTargetPath($targetPath) ->setRedirectType(OptionProvider::PERMANENT) ->setStoreId($storeId) ->setIsAutogenerated(0); + return [$generatedUrl]; } } - return $urls; + return []; } /** * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewrite $url * @param int $storeId - * @return array + * @param \Magento\Catalog\Model\Category|null $category + * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - protected function generateForCustom($url, $storeId) + protected function generateForCustom($url, $storeId, \Magento\Catalog\Model\Category $category = null) { - $urls = []; $targetPath = !$url->getRedirectType() ? $url->getTargetPath() - : $this->categoryUrlPathGenerator->getUrlPathWithSuffix($this->category, $storeId); + : $this->categoryUrlPathGenerator->getUrlPathWithSuffix($category, $storeId); if ($url->getRequestPath() !== $targetPath) { - $urls[$url->getRequestPath() . '_' . $storeId] = $this->urlRewriteFactory->create() - ->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE) - ->setEntityId($this->category->getId()) + $generatedUrl = clone $this->urlRewritePrototype; + $generatedUrl->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE) + ->setEntityId($category->getEntityId()) ->setRequestPath($url->getRequestPath()) ->setTargetPath($targetPath) ->setRedirectType($url->getRedirectType()) @@ -118,7 +141,8 @@ class CurrentUrlRewritesRegenerator ->setDescription($url->getDescription()) ->setIsAutogenerated(0) ->setMetadata($url->getMetadata()); + return [$generatedUrl]; } - return $urls; + return []; } } diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Remove.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Remove.php index e44aa9e0fab09d557544f413994b907dd0184f9a..2617f3290ee0ec801fb1be73e274cc94f13af8b9 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Remove.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Remove.php @@ -5,52 +5,55 @@ */ namespace Magento\CatalogUrlRewrite\Model\Category\Plugin\Category; -use Magento\Catalog\Api\Data\CategoryInterface; -use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider; -use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; -use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; -use Magento\UrlRewrite\Model\UrlPersistInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; class Remove { - /** @var UrlPersistInterface */ + /** @var \Magento\UrlRewrite\Model\UrlPersistInterface */ protected $urlPersist; - /** @var ProductUrlRewriteGenerator */ + /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator */ protected $productUrlRewriteGenerator; - /** @var ChildrenCategoriesProvider */ + /** @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider */ protected $childrenCategoriesProvider; + /** @var \Magento\Framework\Serialize\Serializer\Json */ + private $serializer; + /** - * @param UrlPersistInterface $urlPersist - * @param ProductUrlRewriteGenerator $productUrlRewriteGenerator - * @param ChildrenCategoriesProvider $childrenCategoriesProvider + * @param \Magento\UrlRewrite\Model\UrlPersistInterface $urlPersist + * @param \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator $productUrlRewriteGenerator + * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider + * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer */ public function __construct( - UrlPersistInterface $urlPersist, - ProductUrlRewriteGenerator $productUrlRewriteGenerator, - ChildrenCategoriesProvider $childrenCategoriesProvider + \Magento\UrlRewrite\Model\UrlPersistInterface $urlPersist, + \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator $productUrlRewriteGenerator, + \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider, + \Magento\Framework\Serialize\Serializer\Json $serializer = null ) { $this->urlPersist = $urlPersist; $this->productUrlRewriteGenerator = $productUrlRewriteGenerator; $this->childrenCategoriesProvider = $childrenCategoriesProvider; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\Serialize\Serializer\Json::class + ); } /** * Remove product urls from storage * * @param \Magento\Catalog\Model\ResourceModel\Category $subject - * @param callable $proceed - * @param CategoryInterface $category + * @param \Closure $proceed + * @param \Magento\Catalog\Api\Data\CategoryInterface $category * @return mixed * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function aroundDelete( \Magento\Catalog\Model\ResourceModel\Category $subject, \Closure $proceed, - CategoryInterface $category + \Magento\Catalog\Api\Data\CategoryInterface $category ) { $categoryIds = $this->childrenCategoriesProvider->getChildrenIds($category, true); $categoryIds[] = $category->getId(); @@ -72,13 +75,13 @@ class Remove $this->urlPersist->deleteByData( [ UrlRewrite::ENTITY_ID => $categoryId, - UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE, + UrlRewrite::ENTITY_TYPE => \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator::ENTITY_TYPE, ] ); $this->urlPersist->deleteByData( [ - UrlRewrite::METADATA => serialize(['category_id' => $categoryId]), - UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, + UrlRewrite::METADATA => $this->serializer->serialize(['category_id' => $categoryId]), + UrlRewrite::ENTITY_TYPE => \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator::ENTITY_TYPE, ] ); } diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php index 709baf9e730f9a4ce0dab84799d71b7d39071406..db4b4951a8c8120cc27e6a162399414f9e62c5c4 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php @@ -5,30 +5,30 @@ */ namespace Magento\CatalogUrlRewrite\Model\Category\Plugin; -use Magento\CatalogUrlRewrite\Model\Category\ProductFactory; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; use Magento\UrlRewrite\Model\StorageInterface; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\CatalogUrlRewrite\Model\ResourceModel\Category\Product; class Storage { /** @var UrlFinderInterface */ - protected $urlFinder; + private $urlFinder; - /** @var ProductFactory */ - protected $productFactory; + /** @var Product */ + private $productResource; /** * @param UrlFinderInterface $urlFinder - * @param ProductFactory $productFactory + * @param Product $productResource */ public function __construct( UrlFinderInterface $urlFinder, - ProductFactory $productFactory + Product $productResource ) { $this->urlFinder = $urlFinder; - $this->productFactory = $productFactory; + $this->productResource = $productResource; } /** @@ -50,7 +50,7 @@ class Storage ]; } if ($toSave) { - $this->productFactory->create()->getResource()->saveMultiple($toSave); + $this->productResource->saveMultiple($toSave); } } @@ -62,14 +62,7 @@ class Storage */ public function beforeDeleteByData(StorageInterface $object, array $data) { - $toRemove = []; - $records = $this->urlFinder->findAllByData($data); - foreach ($records as $record) { - $toRemove[] = $record->getUrlRewriteId(); - } - if ($toRemove) { - $this->productFactory->create()->getResource()->removeMultiple($toRemove); - } + $this->productResource->removeMultipleByProductCategory($data); } /** diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php index b3feb4bfd9caa7cec9b29585dbebb3dfcf7bef48..7e5d22399e91f04428ecc60f0503bff70aae0649 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php @@ -35,7 +35,7 @@ class Group /** @var ProductUrlRewriteGenerator */ protected $productUrlRewriteGenerator; - /** @var StoreManagerInterface */ + /** @var StoreManagerInterface */ protected $storeManager; /** diff --git a/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php index bc09d90d461c4862908587f40915be325096a706..e40aa612002693fe50158e5543fee1858ad68fc8 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php @@ -7,11 +7,6 @@ namespace Magento\CatalogUrlRewrite\Model; use Magento\Catalog\Model\Category; use Magento\Catalog\Model\Product; -use Magento\CatalogUrlRewrite\Model\Product\CanonicalUrlRewriteGenerator; -use Magento\CatalogUrlRewrite\Model\Product\CategoriesUrlRewriteGenerator; -use Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator; -use Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator; -use Magento\CatalogUrlRewrite\Service\V1\StoreViewService; use Magento\Store\Model\Store; use Magento\Catalog\Model\Product\Visibility; @@ -28,7 +23,7 @@ class CategoryBasedProductRewriteGenerator private $productScopeRewriteGenerator; /** - * @param ProductScopeRewriteGenerator $productUrlRewriteGenerator + * @param ProductScopeRewriteGenerator $productScopeRewriteGenerator */ public function __construct( ProductScopeRewriteGenerator $productScopeRewriteGenerator @@ -41,9 +36,10 @@ class CategoryBasedProductRewriteGenerator * * @param \Magento\Catalog\Model\Product $product * @param \Magento\Catalog\Model\Category $category + * @param int|null $rootCategoryId * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - public function generate(Product $product, Category $category) + public function generate(Product $product, Category $category, $rootCategoryId = null) { if ($product->getVisibility() == Visibility::VISIBILITY_NOT_VISIBLE) { return []; @@ -52,10 +48,14 @@ class CategoryBasedProductRewriteGenerator $storeId = $product->getStoreId(); $urls = $this->productScopeRewriteGenerator->isGlobalScope($storeId) - ? $this->productScopeRewriteGenerator->generateForGlobalScope([$category], $product) - : $this->productScopeRewriteGenerator->generateForSpecificStoreView($storeId, [$category], $product); + ? $this->productScopeRewriteGenerator->generateForGlobalScope([$category], $product, $rootCategoryId) + : $this->productScopeRewriteGenerator->generateForSpecificStoreView( + $storeId, + [$category], + $product, + $rootCategoryId + ); - $this->product = null; return $urls; } } diff --git a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php index 5748b69ea3ce470d932ed94f0f7009376b64b24d..84ecb5b8a53e89880d772abefaec10356f727cfa 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php @@ -12,6 +12,8 @@ use Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator; use Magento\CatalogUrlRewrite\Service\V1\StoreViewService; use Magento\Store\Model\Store; use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Framework\App\ObjectManager; +use Magento\UrlRewrite\Model\MergeDataProviderFactory; class CategoryUrlRewriteGenerator { @@ -21,7 +23,10 @@ class CategoryUrlRewriteGenerator /** @var StoreViewService */ protected $storeViewService; - /** @var \Magento\Catalog\Model\Category */ + /** + * @var \Magento\Catalog\Model\Category + * @deprecated + */ protected $category; /** @var \Magento\CatalogUrlRewrite\Model\Category\CanonicalUrlRewriteGenerator */ @@ -33,6 +38,9 @@ class CategoryUrlRewriteGenerator /** @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator */ protected $childrenUrlRewriteGenerator; + /** @var \Magento\UrlRewrite\Model\MergeDataProvider */ + private $mergeDataProviderPrototype; + /** * @var bool */ @@ -44,66 +52,83 @@ class CategoryUrlRewriteGenerator * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator $childrenUrlRewriteGenerator * @param \Magento\CatalogUrlRewrite\Service\V1\StoreViewService $storeViewService * @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository + * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory */ public function __construct( CanonicalUrlRewriteGenerator $canonicalUrlRewriteGenerator, CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator, ChildrenUrlRewriteGenerator $childrenUrlRewriteGenerator, StoreViewService $storeViewService, - CategoryRepositoryInterface $categoryRepository + CategoryRepositoryInterface $categoryRepository, + MergeDataProviderFactory $mergeDataProviderFactory = null ) { $this->storeViewService = $storeViewService; $this->canonicalUrlRewriteGenerator = $canonicalUrlRewriteGenerator; $this->childrenUrlRewriteGenerator = $childrenUrlRewriteGenerator; $this->currentUrlRewritesRegenerator = $currentUrlRewritesRegenerator; $this->categoryRepository = $categoryRepository; + if (!isset($mergeDataProviderFactory)) { + $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class); + } + $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); } /** - * {@inheritdoc} + * @param \Magento\Catalog\Model\Category $category + * @param bool $overrideStoreUrls + * @param int|null $rootCategoryId + * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - public function generate($category, $overrideStoreUrls = false) + public function generate($category, $overrideStoreUrls = false, $rootCategoryId = null) { - $this->category = $category; - $this->overrideStoreUrls = $overrideStoreUrls; + if ($rootCategoryId === null) { + $rootCategoryId = $category->getEntityId(); + } - $storeId = $this->category->getStoreId(); + $storeId = $category->getStoreId(); $urls = $this->isGlobalScope($storeId) - ? $this->generateForGlobalScope() - : $this->generateForSpecificStoreView($storeId); + ? $this->generateForGlobalScope($category, $overrideStoreUrls, $rootCategoryId) + : $this->generateForSpecificStoreView($storeId, $category, $rootCategoryId); - $this->category = null; return $urls; } /** * Generate list of urls for global scope * + * @param \Magento\Catalog\Model\Category|null $category + * @param bool $overrideStoreUrls + * @param int|null $rootCategoryId * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - protected function generateForGlobalScope() - { - $urls = []; - $categoryId = $this->category->getId(); - foreach ($this->category->getStoreIds() as $storeId) { + protected function generateForGlobalScope( + Category $category = null, + $overrideStoreUrls = false, + $rootCategoryId = null + ) { + $mergeDataProvider = clone $this->mergeDataProviderPrototype; + $categoryId = $category->getId(); + foreach ($category->getStoreIds() as $storeId) { if (!$this->isGlobalScope($storeId) - && $this->isOverrideUrlsForStore($storeId, $categoryId) + && $this->isOverrideUrlsForStore($storeId, $categoryId, $overrideStoreUrls) ) { - $this->updateCategoryUrlForStore($storeId); - $urls = array_merge($urls, $this->generateForSpecificStoreView($storeId)); + $this->updateCategoryUrlForStore($storeId, $category); + $mergeDataProvider->merge($this->generateForSpecificStoreView($storeId, $category, $rootCategoryId)); } } - return $urls; + $result = $mergeDataProvider->getData(); + return $result; } /** * @param int $storeId * @param int $categoryId + * @param bool $overrideStoreUrls * @return bool */ - protected function isOverrideUrlsForStore($storeId, $categoryId) + protected function isOverrideUrlsForStore($storeId, $categoryId, $overrideStoreUrls = false) { - return $this->overrideStoreUrls || !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore( + return $overrideStoreUrls || !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore( $storeId, $categoryId, Category::ENTITY @@ -114,12 +139,18 @@ class CategoryUrlRewriteGenerator * Override url key and url path for category in specific Store * * @param int $storeId + * @param \Magento\Catalog\Model\Category|null $category * @return void */ - protected function updateCategoryUrlForStore($storeId) + protected function updateCategoryUrlForStore($storeId, Category $category = null) { - $category = $this->categoryRepository->get($this->category->getId(), $storeId); - $this->category->addData(['url_key' => $category->getUrlKey(), 'url_path' => $category->getUrlPath()]); + $categoryFromRepository = $this->categoryRepository->get($category->getId(), $storeId); + $category->addData( + [ + 'url_key' => $categoryFromRepository->getUrlKey(), + 'url_path' => $categoryFromRepository->getUrlPath() + ] + ); } /** @@ -137,15 +168,22 @@ class CategoryUrlRewriteGenerator * Generate list of urls per store * * @param string $storeId + * @param \Magento\Catalog\Model\Category|null $category + * @param int|null $rootCategoryId * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - protected function generateForSpecificStoreView($storeId) + protected function generateForSpecificStoreView($storeId, Category $category = null, $rootCategoryId = null) { - $urls = array_merge( - $this->canonicalUrlRewriteGenerator->generate($storeId, $this->category), - $this->childrenUrlRewriteGenerator->generate($storeId, $this->category), - $this->currentUrlRewritesRegenerator->generate($storeId, $this->category) + $mergeDataProvider = clone $this->mergeDataProviderPrototype; + $mergeDataProvider->merge( + $this->canonicalUrlRewriteGenerator->generate($storeId, $category) ); - return $urls; + $mergeDataProvider->merge( + $this->childrenUrlRewriteGenerator->generate($storeId, $category, $rootCategoryId) + ); + $mergeDataProvider->merge( + $this->currentUrlRewritesRegenerator->generate($storeId, $category, $rootCategoryId) + ); + return $mergeDataProvider->getData(); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryHashMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryHashMap.php new file mode 100644 index 0000000000000000000000000000000000000000..668517f46d89775c32ca28d7882cba0e9d7742f7 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryHashMap.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Model\Map; + +use Magento\Catalog\Model\ResourceModel\CategoryFactory; +use Magento\Catalog\Model\CategoryRepository; +use Magento\Catalog\Api\Data\CategoryInterface; + +/** + * Map that holds data for category ids and its subcategories ids + */ +class DataCategoryHashMap implements HashMapInterface +{ + /** @var int[] */ + private $hashMap = []; + + /** @var CategoryRepository */ + private $categoryRepository; + + /** @var CategoryFactory */ + private $categoryResourceFactory; + + /** + * @param CategoryRepository $categoryRepository + * @param CategoryFactory $categoryResourceFactory + */ + public function __construct( + CategoryRepository $categoryRepository, + CategoryFactory $categoryResourceFactory + ) { + $this->categoryRepository = $categoryRepository; + $this->categoryResourceFactory = $categoryResourceFactory; + } + + /** + * Returns an array of categories ids that includes category identified by $categoryId and all its subcategories + * + * @param int $categoryId + * @return array + */ + public function getAllData($categoryId) + { + if (!isset($this->hashMap[$categoryId])) { + $category = $this->categoryRepository->get($categoryId); + $this->hashMap[$categoryId] = $this->getAllCategoryChildrenIds($category); + } + return $this->hashMap[$categoryId]; + } + + /** + * {@inheritdoc} + */ + public function getData($categoryId, $key) + { + $categorySpecificData = $this->getAllData($categoryId); + if (isset($categorySpecificData[$key])) { + return $categorySpecificData[$key]; + } + return []; + } + + /** + * Queries the database for sub-categories ids from a category + * + * @param CategoryInterface $category + * @return int[] + */ + private function getAllCategoryChildrenIds(CategoryInterface $category) + { + $categoryResource = $this->categoryResourceFactory->create(); + $connection = $categoryResource->getConnection(); + $select = $connection->select() + ->from($categoryResource->getEntityTable(), 'entity_id') + ->where($connection->quoteIdentifier('path') . ' LIKE :c_path'); + $bind = ['c_path' => $category->getPath() . '%']; + return $connection->fetchCol($select, $bind); + } + + /** + * {@inheritdoc} + */ + public function resetData($categoryId) + { + unset($this->hashMap[$categoryId]); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUrlRewriteDatabaseMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUrlRewriteDatabaseMap.php new file mode 100644 index 0000000000000000000000000000000000000000..032ed5e13023e1de62c7c111ed8889d7e6dab6b6 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUrlRewriteDatabaseMap.php @@ -0,0 +1,135 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Model\Map; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\TemporaryTableService; +use Magento\Framework\DB\Select; +use Magento\UrlRewrite\Model\MergeDataProvider; + +/** + * Map that holds data for category url rewrites entity + */ +class DataCategoryUrlRewriteDatabaseMap implements DatabaseMapInterface +{ + const ENTITY_TYPE = 'category'; + + /** @var string[] */ + private $createdTableAdapters = []; + + /** @var HashMapPool */ + private $hashMapPool; + + /** @var ResourceConnection */ + private $connection; + + /** @var TemporaryTableService */ + private $temporaryTableService; + + /** + * @param ResourceConnection $connection + * @param HashMapPool $hashMapPool, + * @param TemporaryTableService $temporaryTableService + */ + public function __construct( + ResourceConnection $connection, + HashMapPool $hashMapPool, + TemporaryTableService $temporaryTableService + ) { + $this->connection = $connection; + $this->hashMapPool = $hashMapPool; + $this->temporaryTableService = $temporaryTableService; + } + + /** + * Generates data from categoryId and stores it into a temporary table + * + * @param int $categoryId + * @return void + */ + private function generateTableAdapter($categoryId) + { + if (!isset($this->createdTableAdapters[$categoryId])) { + $this->createdTableAdapters[$categoryId] = $this->generateData($categoryId); + } + } + + /** + * Queries the database for all category url rewrites that are affected by the category identified by $categoryId + * It returns the name of the temporary table where the resulting data is stored + * + * @param int $categoryId + * @return string + */ + private function generateData($categoryId) + { + $urlRewritesConnection = $this->connection->getConnection(); + $select = $urlRewritesConnection->select() + ->from( + ['e' => $this->connection->getTableName('url_rewrite')], + ['e.*', 'hash_key' => new \Zend_Db_Expr( + "CONCAT(e.store_id,'" . MergeDataProvider::SEPARATOR . "', e.entity_id)" + ) + ] + ) + ->where('entity_type = ?', self::ENTITY_TYPE) + ->where( + $urlRewritesConnection->prepareSqlCondition( + 'entity_id', + [ + 'in' => array_merge( + $this->hashMapPool->getDataMap(DataCategoryUsedInProductsHashMap::class, $categoryId) + ->getAllData($categoryId), + $this->hashMapPool->getDataMap(DataCategoryHashMap::class, $categoryId) + ->getAllData($categoryId) + ) + ] + ) + ); + $mapName = $this->temporaryTableService->createFromSelect( + $select, + $this->connection->getConnection(), + [ + 'PRIMARY' => ['url_rewrite_id'], + 'HASHKEY_ENTITY_STORE' => ['hash_key'], + 'ENTITY_STORE' => ['entity_id', 'store_id'] + ] + ); + return $mapName; + } + + /** + * {@inheritdoc} + */ + public function destroyTableAdapter($categoryId) + { + $this->hashMapPool->resetMap(DataCategoryUsedInProductsHashMap::class, $categoryId); + $this->hashMapPool->resetMap(DataCategoryHashMap::class, $categoryId); + if (isset($this->createdTableAdapters[$categoryId])) { + $this->temporaryTableService->dropTable($this->createdTableAdapters[$categoryId]); + unset($this->createdTableAdapters[$categoryId]); + } + } + + /** + * Gets data by criteria from a map identified by a category Id + * + * @param int $categoryId + * @param string $key + * @return array + */ + public function getData($categoryId, $key) + { + $this->generateTableAdapter($categoryId); + $urlRewritesConnection = $this->connection->getConnection(); + $select = $urlRewritesConnection->select()->from(['e' => $this->createdTableAdapters[$categoryId]]); + if (strlen($key) > 0) { + $select->where('hash_key = ?', $key); + } + + return $urlRewritesConnection->fetchAll($select); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUsedInProductsHashMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUsedInProductsHashMap.php new file mode 100644 index 0000000000000000000000000000000000000000..65200ba1f413d63f06f0d9b319fb6a339d55ae43 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUsedInProductsHashMap.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Model\Map; + +use Magento\Framework\App\ResourceConnection; + +/** + * Map that holds data for categories used by products found in root category + */ +class DataCategoryUsedInProductsHashMap implements HashMapInterface +{ + /** @var int[] */ + private $hashMap = []; + + /** @var HashMapPool */ + private $hashMapPool; + + /** @var ResourceConnection */ + private $connection; + + /** + * @param ResourceConnection $connection + * @param HashMapPool $hashMapPool + */ + public function __construct( + ResourceConnection $connection, + HashMapPool $hashMapPool + ) { + $this->connection = $connection; + $this->hashMapPool = $hashMapPool; + } + + /** + * Returns an array of product ids for all DataProductHashMap list, + * that occur in other categories not part of DataCategoryHashMap list + * + * @param int $categoryId + * @return array + */ + public function getAllData($categoryId) + { + if (!isset($this->hashMap[$categoryId])) { + $productsLinkConnection = $this->connection->getConnection(); + $select = $productsLinkConnection->select() + ->from($this->connection->getTableName('catalog_category_product'), ['category_id']) + ->where( + $productsLinkConnection->prepareSqlCondition( + 'product_id', + [ + 'in' => $this->hashMapPool->getDataMap( + DataProductHashMap::class, + $categoryId + )->getAllData($categoryId) + ] + ) + ) + ->where( + $productsLinkConnection->prepareSqlCondition( + 'category_id', + [ + 'nin' => $this->hashMapPool->getDataMap( + DataCategoryHashMap::class, + $categoryId + )->getAllData($categoryId) + ] + ) + )->group('category_id'); + + $this->hashMap[$categoryId] = $productsLinkConnection->fetchCol($select); + } + return $this->hashMap[$categoryId]; + } + + /** + * {@inheritdoc} + */ + public function getData($categoryId, $key) + { + $categorySpecificData = $this->getAllData($categoryId); + if (isset($categorySpecificData[$key])) { + return $categorySpecificData[$key]; + } + return []; + } + + /** + * {@inheritdoc} + */ + public function resetData($categoryId) + { + $this->hashMapPool->resetMap(DataProductHashMap::class, $categoryId); + $this->hashMapPool->resetMap(DataCategoryHashMap::class, $categoryId); + unset($this->hashMap[$categoryId]); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductHashMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductHashMap.php new file mode 100644 index 0000000000000000000000000000000000000000..4b107043459a3c041181961f5cb0a9c02eeb1a41 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductHashMap.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Model\Map; + +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\Framework\App\ResourceConnection; + +/** + * Map that holds data for products ids from a category and subcategories + */ +class DataProductHashMap implements HashMapInterface +{ + /** @var int[] */ + private $hashMap = []; + + /** @var CollectionFactory */ + private $collectionFactory; + + /** @var HashMapPool */ + private $hashMapPool; + + /** @var ResourceConnection */ + private $connection; + + /** + * @param CollectionFactory $collectionFactory + * @param HashMapPool $hashMapPool + * @param ResourceConnection $connection + */ + public function __construct( + CollectionFactory $collectionFactory, + HashMapPool $hashMapPool, + ResourceConnection $connection + ) { + $this->collectionFactory = $collectionFactory; + $this->hashMapPool = $hashMapPool; + $this->connection = $connection; + } + + /** + * Returns an array of ids of all visible products and assigned to a category and all its subcategories + * + * @param int $categoryId + * @return array + */ + public function getAllData($categoryId) + { + if (!isset($this->hashMap[$categoryId])) { + $productsCollection = $this->collectionFactory->create(); + $productsCollection->getSelect() + ->joinInner( + ['cp' => $this->connection->getTableName('catalog_category_product')], + 'cp.product_id = e.entity_id', + [] + ) + ->where( + $productsCollection->getConnection()->prepareSqlCondition( + 'cp.category_id', + [ + 'in' => $this->hashMapPool->getDataMap( + DataCategoryHashMap::class, + $categoryId + )->getAllData($categoryId) + ] + ) + )->group('e.entity_id'); + $this->hashMap[$categoryId] = $productsCollection->getAllIds(); + } + return $this->hashMap[$categoryId]; + } + + /** + * {@inheritdoc} + */ + public function getData($categoryId, $key) + { + $categorySpecificData = $this->getAllData($categoryId); + if (isset($categorySpecificData[$key])) { + return $categorySpecificData[$key]; + } + return []; + } + + /** + * {@inheritdoc} + */ + public function resetData($categoryId) + { + $this->hashMapPool->resetMap(DataCategoryHashMap::class, $categoryId); + unset($this->hashMap[$categoryId]); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductUrlRewriteDatabaseMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductUrlRewriteDatabaseMap.php new file mode 100644 index 0000000000000000000000000000000000000000..19c701d9445c60884be9cbab2b29839bd24a39b2 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductUrlRewriteDatabaseMap.php @@ -0,0 +1,124 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Model\Map; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\TemporaryTableService; +use Magento\Framework\DB\Select; +use Magento\UrlRewrite\Model\MergeDataProvider; + +/** + * Map that holds data for category url rewrites entity + */ +class DataProductUrlRewriteDatabaseMap implements DatabaseMapInterface +{ + const ENTITY_TYPE = 'product'; + + /** @var string[] */ + private $createdTableAdapters = []; + + /** @var HashMapPool */ + private $hashMapPool; + + /** @var ResourceConnection */ + private $connection; + + /** @var TemporaryTableService */ + private $temporaryTableService; + + /** + * @param ResourceConnection $connection + * @param HashMapPool $hashMapPool, + * @param TemporaryTableService $temporaryTableService + */ + public function __construct( + ResourceConnection $connection, + HashMapPool $hashMapPool, + TemporaryTableService $temporaryTableService + ) { + $this->connection = $connection; + $this->hashMapPool = $hashMapPool; + $this->temporaryTableService = $temporaryTableService; + } + + /** + * Generates data from categoryId and stores it into a temporary table + * + * @param int $categoryId + * @return void + */ + private function generateTableAdapter($categoryId) + { + if (!isset($this->createdTableAdapters[$categoryId])) { + $this->createdTableAdapters[$categoryId] = $this->generateData($categoryId); + } + } + + /** + * {@inheritdoc} + */ + public function getData($categoryId, $key) + { + $this->generateTableAdapter($categoryId); + $urlRewritesConnection = $this->connection->getConnection(); + $select = $urlRewritesConnection->select() + ->from(['e' => $this->createdTableAdapters[$categoryId]]) + ->where('hash_key = ?', $key); + return $urlRewritesConnection->fetchAll($select); + } + + /** + * Queries the database for all category url rewrites that are affected by the category identified by $categoryId + * It returns the name of the temporary table where the resulting data is stored + * + * @param int $categoryId + * @return string + */ + private function generateData($categoryId) + { + $urlRewritesConnection = $this->connection->getConnection(); + $select = $urlRewritesConnection->select() + ->from( + ['e' => $this->connection->getTableName('url_rewrite')], + ['e.*', 'hash_key' => new \Zend_Db_Expr( + "CONCAT(e.store_id,'" . MergeDataProvider::SEPARATOR . "', e.entity_id)" + ) + ] + ) + ->where('entity_type = ?', self::ENTITY_TYPE) + ->where( + $urlRewritesConnection->prepareSqlCondition( + 'entity_id', + [ + 'in' => $this->hashMapPool->getDataMap(DataProductHashMap::class, $categoryId) + ->getAllData($categoryId) + ] + ) + ); + $mapName = $this->temporaryTableService->createFromSelect( + $select, + $this->connection->getConnection(), + [ + 'PRIMARY' => ['url_rewrite_id'], + 'HASHKEY_ENTITY_STORE' => ['hash_key'], + 'ENTITY_STORE' => ['entity_id', 'store_id'] + ] + ); + return $mapName; + } + + /** + * {@inheritdoc} + */ + public function destroyTableAdapter($categoryId) + { + $this->hashMapPool->resetMap(DataProductHashMap::class, $categoryId); + if (isset($this->createdTableAdapters[$categoryId])) { + $this->temporaryTableService->dropTable($this->createdTableAdapters[$categoryId]); + unset($this->createdTableAdapters[$categoryId]); + } + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapInterface.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c2a9a89a33fb5dc689886e5e071c391f99eadd01 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapInterface.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Model\Map; + +use Magento\Framework\DB\Select; + +/** + * Interface for a mysql data type of a map + * + * Is used to get data by a unique key from a temporary table in mysql to prevent memory usage + * It internally holds the knowledge the creation of the actual data and it initializes itself when we call getData + * We should always call destroyTableAdapter when we don't need anymore the temporary tables + */ +interface DatabaseMapInterface +{ + /** + * Gets data by key from a map identified by a category Id + * + * The key is a unique identifier that matches the values of the index used to build the temporary table + * + * Example "1_2" where ids would correspond to store_id entity_id + * + * @param int $categoryId + * @param string $key + * @return array + */ + public function getData($categoryId, $key); + + /** + * Destroys data in the temporary table by categoryId + * It also destroys the data in other maps that are dependencies used to construct the data + * + * @param int $categoryId + * @return void + */ + public function destroyTableAdapter($categoryId); +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapPool.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapPool.php new file mode 100644 index 0000000000000000000000000000000000000000..bb94c1972a17c4e4afa1db555fc461cc21625c95 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapPool.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Model\Map; + +use Magento\Framework\ObjectManagerInterface; + +/** + * Pool for database maps + */ +class DatabaseMapPool +{ + /** + * @var DatabaseMapInterface[] + */ + private $dataArray = []; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * Constructor + * + * @param ObjectManagerInterface $objectManager + */ + public function __construct( + ObjectManagerInterface $objectManager + ) { + $this->objectManager = $objectManager; + } + + /** + * Gets a map by instance and category Id + * + * @param string $instanceName + * @param int $categoryId + * @return DatabaseMapInterface + */ + public function getDataMap($instanceName, $categoryId) + { + $key = $instanceName . '-' . $categoryId; + if (!isset($this->dataArray[$key])) { + $instance = $this->objectManager->create( + $instanceName, + [ + 'category' => $categoryId + ] + ); + if (!$instance instanceof DatabaseMapInterface) { + throw new \InvalidArgumentException( + $instanceName . ' does not implement interface ' . DatabaseMapInterface::class + ); + } + $this->dataArray[$key] = $instance; + } + return $this->dataArray[$key]; + } + + /** + * Resets a database map by instance and category Id + * + * @param string $instanceName + * @param int $categoryId + * @return void + */ + public function resetMap($instanceName, $categoryId) + { + $key = $instanceName . '-' . $categoryId; + if (isset($this->dataArray[$key])) { + $this->dataArray[$key]->destroyTableAdapter($categoryId); + unset($this->dataArray[$key]); + } + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapInterface.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..341cdb1a4fdb1aba3375bace3a687e9d7fd273ee --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapInterface.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Model\Map; + +use Magento\Framework\DB\Select; + +/** + * Interface for a hash data map + * + * It is used for classes that will build hash maps and store them into memory + * The initialization is done transparently whenever getAllData or getData is called + * The map, upon initialization, might have a dependency on some other DataMapInterfaces + * The map has to free memory after we're done using it + * We need to destroy those maps too when calling resetData + */ +interface HashMapInterface +{ + /** + * Gets all data from a map identified by a category Id + * + * @param int $categoryId + * @return array + */ + public function getAllData($categoryId); + + /** + * Gets data by criteria from a map identified by a category Id + * + * @param int $categoryId + * @param string $key + * @return array + */ + public function getData($categoryId, $key); + + /** + * Resets current map by freeing memory and also to its dependencies + * + * @param int $categoryId + * @return void + */ + public function resetData($categoryId); +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapPool.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapPool.php new file mode 100644 index 0000000000000000000000000000000000000000..6606812a61995fa24a383170e67c6c99eb1caa90 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapPool.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Model\Map; + +use Magento\Framework\ObjectManagerInterface; + +/** + * Pool for hash maps + */ +class HashMapPool +{ + /** + * @var HashMapInterface[] + */ + private $dataArray = []; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * Constructor + * + * @param ObjectManagerInterface $objectManager + */ + public function __construct( + ObjectManagerInterface $objectManager + ) { + $this->objectManager = $objectManager; + } + + /** + * Gets a map by instance and category Id + * + * @param string $instanceName + * @param int $categoryId + * @return HashMapInterface + * @throws \Exception + */ + public function getDataMap($instanceName, $categoryId) + { + $key = $instanceName . '-' . $categoryId; + if (!isset($this->dataArray[$key])) { + $instance = $this->objectManager->create( + $instanceName, + [ + 'category' => $categoryId + ] + ); + if (!$instance instanceof HashMapInterface) { + throw new \InvalidArgumentException( + $instanceName . ' does not implement interface ' . HashMapInterface::class + ); + } + $this->dataArray[$key] = $instance; + } + return $this->dataArray[$key]; + } + + /** + * Resets data in a hash map by instance name and category Id + * + * @param string $instanceName + * @param int $categoryId + * @return void + */ + public function resetMap($instanceName, $categoryId) + { + $key = $instanceName . '-' . $categoryId; + if (isset($this->dataArray[$key])) { + $this->dataArray[$key]->resetData($categoryId); + unset($this->dataArray[$key]); + } + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/UrlRewriteFinder.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/UrlRewriteFinder.php new file mode 100644 index 0000000000000000000000000000000000000000..bcadfd848e8830461c380b86aeb32bdc116a6a96 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/UrlRewriteFinder.php @@ -0,0 +1,125 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Model\Map; + +use Magento\Catalog\Model\Product; +use Magento\UrlRewrite\Model\UrlFinderInterface; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; + +/** + * Finds specific queried url rewrites identified by specific fields + * + * A group of identifiers specifies a query consumed by the client to retrieve existing url rewrites from the database + * Clients will query a map of DatabaseMapInterface type through this class resulting into a set of url rewrites results + * Each map type will fallback to a UrlFinderInterface by identifiers for unmapped values + */ +class UrlRewriteFinder +{ + const ENTITY_TYPE_CATEGORY = 'category'; + const ENTITY_TYPE_PRODUCT = 'product'; + + /** @var DatabaseMapPool */ + private $databaseMapPool; + + /** @var UrlFinderInterface */ + private $urlFinder; + + /** @var UrlRewrite */ + private $urlRewritePrototype; + + /** @var array */ + private $urlRewriteClassNames = []; + + /** + * @param DatabaseMapPool $databaseMapPool + * @param UrlFinderInterface $urlFinder + * @param UrlRewriteFactory $urlRewriteFactory + * @param string[] $urlRewriteClassNames + */ + public function __construct( + DatabaseMapPool $databaseMapPool, + UrlFinderInterface $urlFinder, + UrlRewriteFactory $urlRewriteFactory, + array $urlRewriteClassNames = [] + ) { + $this->databaseMapPool = $databaseMapPool; + $this->urlFinder = $urlFinder; + $this->urlRewriteClassNames = $urlRewriteClassNames; + $this->urlRewritePrototype = $urlRewriteFactory->create(); + } + + /** + * Retrieves existing url rewrites filtered by identifiers from prebuild database maps + * This method will fall-back to by using UrlFinderInterface when map type is not found in configured list + * + * @param int $entityId + * @param int $storeId + * @param string $entityType + * @param int|null $rootCategoryId + * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] + */ + public function findAllByData($entityId, $storeId, $entityType, $rootCategoryId = null) + { + if ($rootCategoryId + && is_numeric($entityId) + && is_numeric($storeId) + && is_string($entityType) + && isset($this->urlRewriteClassNames[$entityType]) + ) { + $map = $this->databaseMapPool->getDataMap($this->urlRewriteClassNames[$entityType], $rootCategoryId); + if ($map) { + $key = $storeId . '_' . $entityId; + return $this->arrayToUrlRewriteObject($map->getData($rootCategoryId, $key)); + } + } + + return $this->urlFinder->findAllByData( + [ + UrlRewrite::STORE_ID => $storeId, + UrlRewrite::ENTITY_ID => $entityId, + UrlRewrite::ENTITY_TYPE => $entityType + ] + ); + } + + /** + * Transfers an array values to url rewrite object values + * + * @param array $data + * @return UrlRewrite[] + */ + private function arrayToUrlRewriteObject(array $data) + { + foreach ($data as $key => $array) { + $data[$key] = $this->createUrlRewrite($array); + } + return $data; + } + + /** + * Creates url rewrite object and sets $data to its properties by key->value + * + * @param array $data + * @return UrlRewrite + */ + private function createUrlRewrite(array $data) + { + $dataObject = clone $this->urlRewritePrototype; + $dataObject->setUrlRewriteId($data['url_rewrite_id']); + $dataObject->setEntityType($data['entity_type']); + $dataObject->setEntityId($data['entity_id']); + $dataObject->setRequestPath($data['request_path']); + $dataObject->setTargetPath($data['target_path']); + $dataObject->setRedirectType($data['redirect_type']); + $dataObject->setStoreId($data['store_id']); + $dataObject->setDescription($data['description']); + $dataObject->setIsAutogenerated($data['is_autogenerated']); + $dataObject->setMetadata($data['metadata']); + + return $dataObject; + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php index 5f1f01f7b08a83222dd84df95838b69e0031a079..dee95f236841ef6c3c4ceb5e32eaf4de8cdda238 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php @@ -14,20 +14,31 @@ use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; -use Magento\Store\Model\StoreManagerInterface; +use Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder; +use Magento\Framework\App\ObjectManager; +use Magento\UrlRewrite\Model\MergeDataProviderFactory; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CurrentUrlRewritesRegenerator { - /** @var Product */ + /** + * @var Product + * @deprecated + */ protected $product; - /** @var ObjectRegistry */ + /** + * @var ObjectRegistry + * @deprecated + */ protected $productCategories; - /** @var UrlFinderInterface */ + /** + * @var UrlFinderInterface + * @deprecated + */ protected $urlFinder; /** @var ProductUrlPathGenerator */ @@ -36,19 +47,38 @@ class CurrentUrlRewritesRegenerator /** @var UrlRewriteFactory */ protected $urlRewriteFactory; + /** @var UrlRewrite */ + private $urlRewritePrototype; + + /** @var UrlRewriteFinder */ + private $urlRewriteFinder; + + /** @var \Magento\UrlRewrite\Model\MergeDataProvider */ + private $mergeDataProviderPrototype; + /** * @param UrlFinderInterface $urlFinder * @param ProductUrlPathGenerator $productUrlPathGenerator * @param UrlRewriteFactory $urlRewriteFactory + * @param UrlRewriteFinder|null $urlRewriteFinder + * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory */ public function __construct( UrlFinderInterface $urlFinder, ProductUrlPathGenerator $productUrlPathGenerator, - UrlRewriteFactory $urlRewriteFactory + UrlRewriteFactory $urlRewriteFactory, + UrlRewriteFinder $urlRewriteFinder = null, + MergeDataProviderFactory $mergeDataProviderFactory = null ) { $this->urlFinder = $urlFinder; $this->productUrlPathGenerator = $productUrlPathGenerator; $this->urlRewriteFactory = $urlRewriteFactory; + $this->urlRewritePrototype = $urlRewriteFactory->create(); + $this->urlRewriteFinder = $urlRewriteFinder ?: ObjectManager::getInstance()->get(UrlRewriteFinder::class); + if (!isset($mergeDataProviderFactory)) { + $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class); + } + $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); } /** @@ -57,104 +87,100 @@ class CurrentUrlRewritesRegenerator * @param int $storeId * @param Product $product * @param ObjectRegistry $productCategories + * @param int|null $rootCategoryId * @return UrlRewrite[] */ - public function generate($storeId, Product $product, ObjectRegistry $productCategories) + public function generate($storeId, Product $product, ObjectRegistry $productCategories, $rootCategoryId = null) { - $this->product = $product; - $this->productCategories = $productCategories; - - $currentUrlRewrites = $this->urlFinder->findAllByData( - [ - UrlRewrite::STORE_ID => $storeId, - UrlRewrite::ENTITY_ID => $this->product->getId(), - UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, - ] + $mergeDataProvider = clone $this->mergeDataProviderPrototype; + $currentUrlRewrites = $this->urlRewriteFinder->findAllByData( + $product->getEntityId(), + $storeId, + ProductUrlRewriteGenerator::ENTITY_TYPE, + $rootCategoryId ); - $urlRewrites = []; foreach ($currentUrlRewrites as $currentUrlRewrite) { - $category = $this->retrieveCategoryFromMetadata($currentUrlRewrite); + $category = $this->retrieveCategoryFromMetadata($currentUrlRewrite, $productCategories); if ($category === false) { continue; } - $url = $currentUrlRewrite->getIsAutogenerated() - ? $this->generateForAutogenerated($currentUrlRewrite, $storeId, $category) - : $this->generateForCustom($currentUrlRewrite, $storeId, $category); - $urlRewrites = array_merge($urlRewrites, $url); + $mergeDataProvider->merge( + $currentUrlRewrite->getIsAutogenerated() + ? $this->generateForAutogenerated($currentUrlRewrite, $storeId, $category, $product) + : $this->generateForCustom($currentUrlRewrite, $storeId, $category, $product) + ); } - $this->product = null; - $this->productCategories = null; - return $urlRewrites; + return $mergeDataProvider->getData(); } /** * @param UrlRewrite $url * @param int $storeId * @param Category|null $category - * @return array + * @param Product|null $product + * @return UrlRewrite[] */ - protected function generateForAutogenerated($url, $storeId, $category) + protected function generateForAutogenerated($url, $storeId, $category, $product = null) { - if (!$this->product->getData('save_rewrites_history')) { - return []; - } - $targetPath = $this->productUrlPathGenerator->getUrlPathWithSuffix($this->product, $storeId, $category); - if ($url->getRequestPath() === $targetPath) { - return []; + if ($product->getData('save_rewrites_history')) { + $targetPath = $this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId, $category); + if ($url->getRequestPath() !== $targetPath) { + $generatedUrl = clone $this->urlRewritePrototype; + $generatedUrl->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE) + ->setEntityId($product->getEntityId()) + ->setRequestPath($url->getRequestPath()) + ->setTargetPath($targetPath) + ->setRedirectType(OptionProvider::PERMANENT) + ->setStoreId($storeId) + ->setDescription($url->getDescription()) + ->setIsAutogenerated(0) + ->setMetadata($url->getMetadata()); + return [$generatedUrl]; + } } - return [ - $this->urlRewriteFactory->create() - ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE) - ->setEntityId($this->product->getId()) - ->setRequestPath($url->getRequestPath()) - ->setTargetPath($targetPath) - ->setRedirectType(OptionProvider::PERMANENT) - ->setStoreId($storeId) - ->setDescription($url->getDescription()) - ->setIsAutogenerated(0) - ->setMetadata($url->getMetadata()) - ]; + return []; } /** * @param UrlRewrite $url * @param int $storeId * @param Category|null $category - * @return array + * @param Product|null $product + * @return UrlRewrite[] */ - protected function generateForCustom($url, $storeId, $category) + protected function generateForCustom($url, $storeId, $category, $product = null) { $targetPath = $url->getRedirectType() - ? $this->productUrlPathGenerator->getUrlPathWithSuffix($this->product, $storeId, $category) + ? $this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId, $category) : $url->getTargetPath(); - if ($url->getRequestPath() === $targetPath) { - return []; - } - return [ - $this->urlRewriteFactory->create() - ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE) - ->setEntityId($this->product->getId()) + if ($url->getRequestPath() !== $targetPath) { + $generatedUrl = clone $this->urlRewritePrototype; + $generatedUrl->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE) + ->setEntityId($product->getEntityId()) ->setRequestPath($url->getRequestPath()) ->setTargetPath($targetPath) ->setRedirectType($url->getRedirectType()) ->setStoreId($storeId) ->setDescription($url->getDescription()) ->setIsAutogenerated(0) - ->setMetadata($url->getMetadata()) - ]; + ->setMetadata($url->getMetadata()); + return [$generatedUrl]; + } + return []; } /** * @param UrlRewrite $url + * @param ObjectRegistry|null $productCategories * @return Category|null|bool */ - protected function retrieveCategoryFromMetadata($url) + protected function retrieveCategoryFromMetadata($url, ObjectRegistry $productCategories = null) { $metadata = $url->getMetadata(); if (isset($metadata['category_id'])) { - $category = $this->productCategories->get($metadata['category_id']); + $category = $productCategories->get($metadata['category_id']); return $category === null ? false : $category; } return null; diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php index 5383709ade6e0d250a4086d6adc2b4a7bb6e1f4b..6e357459de8d3790c9e57624c1a7dbb1a9910268 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php @@ -14,6 +14,8 @@ use Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator; use Magento\CatalogUrlRewrite\Service\V1\StoreViewService; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; +use Magento\UrlRewrite\Model\MergeDataProviderFactory; +use Magento\Framework\App\ObjectManager; /** * Class ProductScopeRewriteGenerator @@ -56,6 +58,9 @@ class ProductScopeRewriteGenerator */ private $canonicalUrlRewriteGenerator; + /** @var \Magento\UrlRewrite\Model\MergeDataProvider */ + private $mergeDataProviderPrototype; + /** * @param StoreViewService $storeViewService * @param StoreManagerInterface $storeManager @@ -64,6 +69,7 @@ class ProductScopeRewriteGenerator * @param CategoriesUrlRewriteGenerator $categoriesUrlRewriteGenerator * @param CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator * @param AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator + * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory */ public function __construct( StoreViewService $storeViewService, @@ -72,7 +78,8 @@ class ProductScopeRewriteGenerator CanonicalUrlRewriteGenerator $canonicalUrlRewriteGenerator, CategoriesUrlRewriteGenerator $categoriesUrlRewriteGenerator, CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator, - AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator + AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator, + MergeDataProviderFactory $mergeDataProviderFactory = null ) { $this->storeViewService = $storeViewService; $this->storeManager = $storeManager; @@ -81,6 +88,10 @@ class ProductScopeRewriteGenerator $this->categoriesUrlRewriteGenerator = $categoriesUrlRewriteGenerator; $this->currentUrlRewritesRegenerator = $currentUrlRewritesRegenerator; $this->anchorUrlRewriteGenerator = $anchorUrlRewriteGenerator; + if (!isset($mergeDataProviderFactory)) { + $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class); + } + $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); } /** @@ -97,36 +108,44 @@ class ProductScopeRewriteGenerator /** * Generate url rewrites for global scope * + * @param \Magento\Framework\Data\Collection|\Magento\Catalog\Model\Category[] $productCategories * @param Product $product - * @param \Magento\Framework\Data\Collection $productCategories + * @param int|null $rootCategoryId * @return array */ - public function generateForGlobalScope($productCategories, Product $product) + public function generateForGlobalScope($productCategories, Product $product, $rootCategoryId = null) { - $urls = []; $productId = $product->getEntityId(); + $mergeDataProvider = clone $this->mergeDataProviderPrototype; foreach ($product->getStoreIds() as $id) { - if (!$this->isGlobalScope($id) - && !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore($id, $productId, Product::ENTITY) - ) { - $urls = array_merge($urls, $this->generateForSpecificStoreView($id, $productCategories, $product)); + if (!$this->isGlobalScope($id) && + !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore( + $id, + $productId, + Product::ENTITY + )) { + $mergeDataProvider->merge( + $this->generateForSpecificStoreView($id, $productCategories, $product, $rootCategoryId) + ); } } - return $urls; + return $mergeDataProvider->getData(); } /** * Generate list of urls for specific store view * * @param int $storeId - * @param \Magento\Framework\Data\Collection $productCategories + * @param \Magento\Framework\Data\Collection|Category[] $productCategories * @param \Magento\Catalog\Model\Product $product + * @param int|null $rootCategoryId * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - public function generateForSpecificStoreView($storeId, $productCategories, Product $product) + public function generateForSpecificStoreView($storeId, $productCategories, Product $product, $rootCategoryId = null) { + $mergeDataProvider = clone $this->mergeDataProviderPrototype; $categories = []; foreach ($productCategories as $category) { if ($this->isCategoryProperForGenerating($category, $storeId)) { @@ -134,23 +153,26 @@ class ProductScopeRewriteGenerator } } $productCategories = $this->objectRegistryFactory->create(['entities' => $categories]); - /** - * @var $urls \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] - */ - $urls = array_merge( - $this->canonicalUrlRewriteGenerator->generate($storeId, $product), - $this->categoriesUrlRewriteGenerator->generate($storeId, $product, $productCategories), - $this->currentUrlRewritesRegenerator->generate($storeId, $product, $productCategories), + + $mergeDataProvider->merge( + $this->canonicalUrlRewriteGenerator->generate($storeId, $product) + ); + $mergeDataProvider->merge( + $this->categoriesUrlRewriteGenerator->generate($storeId, $product, $productCategories) + ); + $mergeDataProvider->merge( + $this->currentUrlRewritesRegenerator->generate( + $storeId, + $product, + $productCategories, + $rootCategoryId + ) + ); + $mergeDataProvider->merge( $this->anchorUrlRewriteGenerator->generate($storeId, $product, $productCategories) ); - /* Reduce duplicates. Last wins */ - $result = []; - foreach ($urls as $url) { - $result[$url->getTargetPath() . '-' . $url->getStoreId()] = $url; - } - $this->productCategories = null; - return $result; + return $mergeDataProvider->getData(); } /** diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php index 0ff4877405477f163060443ef08de7bd71c2d4bb..a45b33bcc52a2570030c83a8b3052808a9aad144 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php @@ -31,7 +31,10 @@ class ProductUrlRewriteGenerator */ protected $storeViewService; - /** @var \Magento\Catalog\Model\Product */ + /** + * @var \Magento\Catalog\Model\Product + * @deprecated + */ protected $product; /** @@ -119,26 +122,25 @@ class ProductUrlRewriteGenerator * Generate product url rewrites * * @param \Magento\Catalog\Model\Product $product + * @param int|null $rootCategoryId * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - public function generate(Product $product) + public function generate(Product $product, $rootCategoryId = null) { if ($product->getVisibility() == Visibility::VISIBILITY_NOT_VISIBLE) { return []; } - $this->product = $product; - $storeId = $this->product->getStoreId(); + $storeId = $product->getStoreId(); $productCategories = $product->getCategoryCollection() ->addAttributeToSelect('url_key') ->addAttributeToSelect('url_path'); $urls = $this->isGlobalScope($storeId) - ? $this->generateForGlobalScope($productCategories) - : $this->generateForSpecificStoreView($storeId, $productCategories); + ? $this->generateForGlobalScope($productCategories, $product, $rootCategoryId) + : $this->generateForSpecificStoreView($storeId, $productCategories, $product, $rootCategoryId); - $this->product = null; return $urls; } @@ -159,11 +161,17 @@ class ProductUrlRewriteGenerator * * @deprecated * @param \Magento\Framework\Data\Collection $productCategories + * @param \Magento\Catalog\Model\Product|null $product + * @param int|null $rootCategoryId * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - protected function generateForGlobalScope($productCategories) + protected function generateForGlobalScope($productCategories, $product = null, $rootCategoryId = null) { - return $this->getProductScopeRewriteGenerator()->generateForGlobalScope($productCategories, $this->product); + return $this->getProductScopeRewriteGenerator()->generateForGlobalScope( + $productCategories, + $product, + $rootCategoryId + ); } /** @@ -172,12 +180,18 @@ class ProductUrlRewriteGenerator * @deprecated * @param int $storeId * @param \Magento\Framework\Data\Collection $productCategories + * @param Product|null $product + * @param int|null $rootCategoryId * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] */ - protected function generateForSpecificStoreView($storeId, $productCategories) - { + protected function generateForSpecificStoreView( + $storeId, + $productCategories, + $product = null, + $rootCategoryId = null + ) { return $this->getProductScopeRewriteGenerator() - ->generateForSpecificStoreView($storeId, $productCategories, $this->product); + ->generateForSpecificStoreView($storeId, $productCategories, $product, $rootCategoryId); } /** diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php index f2043d305c4e8545e1d01e4da7cf0c9c0f511640..ead44dca8a0df9040de8f4e2b1f8c710e064518e 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php @@ -6,6 +6,7 @@ namespace Magento\CatalogUrlRewrite\Model\ResourceModel\Category; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\UrlRewrite\Model\Storage\DbStorage; class Product extends AbstractDb { @@ -55,6 +56,8 @@ class Product extends AbstractDb } /** + * Removes data by primary key + * * @param array $removeData * @return int */ @@ -65,4 +68,36 @@ class Product extends AbstractDb ['url_rewrite_id in (?)' => $removeData] ); } + + /** + * Removes multiple entities from url_rewrite table using entities from catalog_url_rewrite_product_category + * Example: $filter = ['category_id' => [1, 2, 3], 'product_id' => [1, 2, 3]] + * + * @param array $filter + * @return int + */ + public function removeMultipleByProductCategory(array $filter) + { + return $this->getConnection()->delete( + $this->getTable(self::TABLE_NAME), + ['url_rewrite_id in (?)' => $this->prepareSelect($filter)] + ); + } + + /** + * Prepare select statement for specific filter + * + * @param array $data + * @return \Magento\Framework\DB\Select + */ + private function prepareSelect($data) + { + $select = $this->getConnection()->select(); + $select->from($this->getTable(DbStorage::TABLE_NAME), 'url_rewrite_id'); + + foreach ($data as $column => $value) { + $select->where($this->getConnection()->quoteIdentifier($column) . ' IN (?)', $value); + } + return $select; + } } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php index a4847fa11b1c40911d08831adbf86553d39fc90f..f76db7bef44d5b81936015bb033990c36205bc54 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php @@ -20,6 +20,9 @@ use Magento\UrlRewrite\Model\OptionProvider; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\Framework\Event\ObserverInterface; use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\App\ObjectManager; +use Magento\UrlRewrite\Model\MergeDataProviderFactory; + /** * Class AfterImportDataObserver * @@ -96,6 +99,9 @@ class AfterImportDataObserver implements ObserverInterface 'visibility', ]; + /** @var \Magento\UrlRewrite\Model\MergeDataProvider */ + private $mergeDataProviderPrototype; + /** * @param \Magento\Catalog\Model\ProductFactory $catalogProductFactory * @param \Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory $objectRegistryFactory @@ -105,6 +111,7 @@ class AfterImportDataObserver implements ObserverInterface * @param UrlPersistInterface $urlPersist * @param UrlRewriteFactory $urlRewriteFactory * @param UrlFinderInterface $urlFinder + * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory * @throws \InvalidArgumentException * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -116,7 +123,8 @@ class AfterImportDataObserver implements ObserverInterface \Magento\Store\Model\StoreManagerInterface $storeManager, UrlPersistInterface $urlPersist, UrlRewriteFactory $urlRewriteFactory, - UrlFinderInterface $urlFinder + UrlFinderInterface $urlFinder, + MergeDataProviderFactory $mergeDataProviderFactory = null ) { $this->urlPersist = $urlPersist; $this->catalogProductFactory = $catalogProductFactory; @@ -126,6 +134,10 @@ class AfterImportDataObserver implements ObserverInterface $this->storeManager = $storeManager; $this->urlRewriteFactory = $urlRewriteFactory; $this->urlFinder = $urlFinder; + if (!isset($mergeDataProviderFactory)) { + $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class); + } + $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); } /** @@ -258,24 +270,16 @@ class AfterImportDataObserver implements ObserverInterface */ protected function generateUrls() { - /** - * @var $urls \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] - */ - $urls = array_merge( - $this->canonicalUrlRewriteGenerate(), - $this->categoriesUrlRewriteGenerate(), - $this->currentUrlRewritesRegenerate() - ); - - /* Reduce duplicates. Last wins */ - $result = []; - foreach ($urls as $url) { - $result[$url->getTargetPath() . '-' . $url->getStoreId()] = $url; - } + $mergeDataProvider = clone $this->mergeDataProviderPrototype; + $mergeDataProvider->merge($this->canonicalUrlRewriteGenerate()); + $mergeDataProvider->merge($this->categoriesUrlRewriteGenerate()); + $mergeDataProvider->merge($this->currentUrlRewritesRegenerate()); $this->productCategories = null; + unset($this->products); $this->products = []; - return $result; + + return $mergeDataProvider->getData(); } /** diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php index 5a4946d50526aa6d3bb7ed3e61dcf135863cc52a..dd0250049a426187f674ac61ad60e8c78ac02f67 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php @@ -8,54 +8,50 @@ namespace Magento\CatalogUrlRewrite\Observer; use Magento\Catalog\Model\Category; use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; use Magento\CatalogUrlRewrite\Model\UrlRewriteBunchReplacer; -use Magento\Framework\App\ObjectManager; -use Magento\UrlRewrite\Model\UrlPersistInterface; use Magento\Framework\Event\ObserverInterface; +use Magento\CatalogUrlRewrite\Model\Map\DatabaseMapPool; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap; +use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap; class CategoryProcessUrlRewriteSavingObserver implements ObserverInterface { /** @var CategoryUrlRewriteGenerator */ - protected $categoryUrlRewriteGenerator; + private $categoryUrlRewriteGenerator; - /** @var UrlPersistInterface */ - protected $urlPersist; - - /** - * @var UrlRewriteBunchReplacer - */ + /** @var UrlRewriteBunchReplacer */ private $urlRewriteBunchReplacer; /** @var UrlRewriteHandler */ - protected $urlRewriteHandler; + private $urlRewriteHandler; + + /** @var DatabaseMapPool */ + private $databaseMapPool; + + /** @var string[] */ + private $dataUrlRewriteClassNames; /** * @param CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator - * @param UrlPersistInterface $urlPersist * @param UrlRewriteHandler $urlRewriteHandler + * @param UrlRewriteBunchReplacer $urlRewriteBunchReplacer + * @param DatabaseMapPool $databaseMapPool + * @param string[] $dataUrlRewriteClassNames */ public function __construct( CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator, - UrlPersistInterface $urlPersist, - UrlRewriteHandler $urlRewriteHandler + UrlRewriteHandler $urlRewriteHandler, + UrlRewriteBunchReplacer $urlRewriteBunchReplacer, + DatabaseMapPool $databaseMapPool, + $dataUrlRewriteClassNames = [ + DataCategoryUrlRewriteDatabaseMap::class, + DataProductUrlRewriteDatabaseMap::class + ] ) { $this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator; - $this->urlPersist = $urlPersist; $this->urlRewriteHandler = $urlRewriteHandler; - } - - /** - * Retrieve Url Rewrite Replacer based on bunches - * - * @deprecated - * @return UrlRewriteBunchReplacer - */ - private function getUrlRewriteBunchReplacer() - { - if (!$this->urlRewriteBunchReplacer) { - $this->urlRewriteBunchReplacer = ObjectManager::getInstance()->get(UrlRewriteBunchReplacer::class); - } - - return $this->urlRewriteBunchReplacer; + $this->urlRewriteBunchReplacer = $urlRewriteBunchReplacer; + $this->databaseMapPool = $databaseMapPool; + $this->dataUrlRewriteClassNames = $dataUrlRewriteClassNames; } /** @@ -67,7 +63,7 @@ class CategoryProcessUrlRewriteSavingObserver implements ObserverInterface public function execute(\Magento\Framework\Event\Observer $observer) { /** @var Category $category */ - $category = $observer->getEvent()->getCategory(); + $category = $observer->getEvent()->getData('category'); if ($category->getParentId() == Category::TREE_ROOT_ID) { return; } @@ -75,12 +71,28 @@ class CategoryProcessUrlRewriteSavingObserver implements ObserverInterface || $category->dataHasChangedFor('is_anchor') || $category->getIsChangedProductList() ) { - $urlRewrites = array_merge( - $this->categoryUrlRewriteGenerator->generate($category), - $this->urlRewriteHandler->generateProductUrlRewrites($category) - ); + $categoryUrlRewriteResult = $this->categoryUrlRewriteGenerator->generate($category); + $this->urlRewriteBunchReplacer->doBunchReplace($categoryUrlRewriteResult); + + $productUrlRewriteResult = $this->urlRewriteHandler->generateProductUrlRewrites($category); + $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); - $this->getUrlRewriteBunchReplacer()->doBunchReplace($urlRewrites); + //frees memory for maps that are self-initialized in multiple classes that were called by the generators + $this->resetUrlRewritesDataMaps($category); } } + + /** + * Resets used data maps to free up memory and temporary tables + * + * @param Category $category + * @return void + */ + private function resetUrlRewritesDataMaps($category) + { + foreach ($this->dataUrlRewriteClassNames as $className) { + $this->databaseMapPool->resetMap($className, $category->getEntityId()); + } + + } } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php index ff3ca37f2e23ee7b7b3ab74f7e1f3b8a13e734eb..9d53150675518ced86b210d17367fdc3fec714a1 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php @@ -5,27 +5,18 @@ */ namespace Magento\CatalogUrlRewrite\Observer; -use Magento\Catalog\Model\Category; -use Magento\CatalogUrlRewrite\Model\CategoryBasedProductRewriteGenerator; -use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; -use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Event\Observer as EventObserver; -use Magento\UrlRewrite\Model\UrlPersistInterface; -use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; - class UrlRewriteHandler { /** @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider */ protected $childrenCategoriesProvider; - /** @var CategoryUrlRewriteGenerator */ + /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator */ protected $categoryUrlRewriteGenerator; - /** @var ProductUrlRewriteGenerator */ + /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator */ protected $productUrlRewriteGenerator; - /** @var UrlPersistInterface */ + /** @var \Magento\UrlRewrite\Model\UrlPersistInterface */ protected $urlPersist; /** @var array */ @@ -34,44 +25,64 @@ class UrlRewriteHandler /** @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory */ protected $productCollectionFactory; - /** - * @var CategoryBasedProductRewriteGenerator - */ + /** @var \Magento\CatalogUrlRewrite\Model\CategoryBasedProductRewriteGenerator */ private $categoryBasedProductRewriteGenerator; + /** @var \Magento\UrlRewrite\Model\MergeDataProvider */ + private $mergeDataProviderPrototype; + + /** @var \Magento\Framework\Serialize\Serializer\Json */ + private $serializer; + /** * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider - * @param CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator - * @param ProductUrlRewriteGenerator $productUrlRewriteGenerator - * @param UrlPersistInterface $urlPersist + * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator + * @param \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator $productUrlRewriteGenerator + * @param \Magento\UrlRewrite\Model\UrlPersistInterface $urlPersist * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory + * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory + * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer */ public function __construct( \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider, - CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator, - ProductUrlRewriteGenerator $productUrlRewriteGenerator, - UrlPersistInterface $urlPersist, - \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory + \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator, + \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator $productUrlRewriteGenerator, + \Magento\UrlRewrite\Model\UrlPersistInterface $urlPersist, + \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, + \Magento\UrlRewrite\Model\MergeDataProviderFactory $mergeDataProviderFactory = null, + \Magento\Framework\Serialize\Serializer\Json $serializer = null ) { $this->childrenCategoriesProvider = $childrenCategoriesProvider; $this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator; $this->productUrlRewriteGenerator = $productUrlRewriteGenerator; $this->urlPersist = $urlPersist; $this->productCollectionFactory = $productCollectionFactory; + + if (!isset($mergeDataProviderFactory)) { + $mergeDataProviderFactory = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\UrlRewrite\Model\MergeDataProviderFactory::class + ); + } + + $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); + + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\Serialize\Serializer\Json::class + ); } /** * Generate url rewrites for products assigned to category * - * @param Category $category + * @param \Magento\Catalog\Model\Category $category * @return array */ - public function generateProductUrlRewrites(Category $category) + public function generateProductUrlRewrites(\Magento\Catalog\Model\Category $category) { + $mergeDataProvider = clone $this->mergeDataProviderPrototype; $this->isSkippedProduct = []; $saveRewriteHistory = $category->getData('save_rewrites_history'); $storeId = $category->getStoreId(); - $productUrls = []; if ($category->getAffectedProductIds()) { $this->isSkippedProduct = $category->getAffectedProductIds(); $collection = $this->productCollectionFactory->create() @@ -84,38 +95,54 @@ class UrlRewriteHandler foreach ($collection as $product) { $product->setStoreId($storeId); $product->setData('save_rewrites_history', $saveRewriteHistory); - $productUrls = array_merge($productUrls, $this->productUrlRewriteGenerator->generate($product)); + $mergeDataProvider->merge( + $this->productUrlRewriteGenerator->generate($product, $category->getEntityId()) + ); } } else { - $productUrls = array_merge( - $productUrls, - $this->getCategoryProductsUrlRewrites($category, $storeId, $saveRewriteHistory) + $mergeDataProvider->merge( + $this->getCategoryProductsUrlRewrites( + $category, + $storeId, + $saveRewriteHistory, + $category->getEntityId() + ) ); } foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) { - $productUrls = array_merge( - $productUrls, - $this->getCategoryProductsUrlRewrites($childCategory, $storeId, $saveRewriteHistory) + $mergeDataProvider->merge( + $this->getCategoryProductsUrlRewrites( + $childCategory, + $storeId, + $saveRewriteHistory, + $category->getEntityId() + ) ); } - return $productUrls; + + return $mergeDataProvider->getData(); } /** - * @param Category $category + * @param \Magento\Catalog\Model\Category $category * @param int $storeId * @param bool $saveRewriteHistory - * @return UrlRewrite[] + * @param int|null $rootCategoryId + * @return array */ - public function getCategoryProductsUrlRewrites(Category $category, $storeId, $saveRewriteHistory) - { + public function getCategoryProductsUrlRewrites( + \Magento\Catalog\Model\Category $category, + $storeId, + $saveRewriteHistory, + $rootCategoryId = null + ) { + $mergeDataProvider = clone $this->mergeDataProviderPrototype; /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */ $productCollection = $category->getProductCollection() ->addAttributeToSelect('name') ->addAttributeToSelect('visibility') ->addAttributeToSelect('url_key') ->addAttributeToSelect('url_path'); - $productUrls = []; foreach ($productCollection as $product) { if (in_array($product->getId(), $this->isSkippedProduct)) { continue; @@ -123,49 +150,53 @@ class UrlRewriteHandler $this->isSkippedProduct[] = $product->getId(); $product->setStoreId($storeId); $product->setData('save_rewrites_history', $saveRewriteHistory); - $productUrls = array_merge( - $productUrls, - $this->getCategoryBasedProductRewriteGenerator()->generate($product, $category) + $mergeDataProvider->merge( + $this->getCategoryBasedProductRewriteGenerator()->generate($product, $category, $rootCategoryId) ); } - return $productUrls; + + return $mergeDataProvider->getData(); } /** * Retrieve generator, which use single category for different products * * @deprecated - * @return CategoryBasedProductRewriteGenerator|mixed + * @return \Magento\CatalogUrlRewrite\Model\CategoryBasedProductRewriteGenerator|mixed */ private function getCategoryBasedProductRewriteGenerator() { if (!$this->categoryBasedProductRewriteGenerator) { - $this->categoryBasedProductRewriteGenerator = ObjectManager::getInstance() - ->get(CategoryBasedProductRewriteGenerator::class); + $this->categoryBasedProductRewriteGenerator = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\CatalogUrlRewrite\Model\CategoryBasedProductRewriteGenerator::class); } return $this->categoryBasedProductRewriteGenerator; } /** - * @param Category $category + * @param \Magento\Catalog\Model\Category $category * @return void */ - public function deleteCategoryRewritesForChildren(Category $category) + public function deleteCategoryRewritesForChildren(\Magento\Catalog\Model\Category $category) { $categoryIds = $this->childrenCategoriesProvider->getChildrenIds($category, true); $categoryIds[] = $category->getId(); foreach ($categoryIds as $categoryId) { $this->urlPersist->deleteByData( [ - UrlRewrite::ENTITY_ID => $categoryId, - UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE, + \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::ENTITY_ID => + $categoryId, + \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::ENTITY_TYPE => + \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator::ENTITY_TYPE, ] ); $this->urlPersist->deleteByData( [ - UrlRewrite::METADATA => serialize(['category_id' => $categoryId]), - UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, + \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::METADATA => + $this->serializer->serialize(['category_id' => $categoryId]), + \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::ENTITY_TYPE => + \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator::ENTITY_TYPE, ] ); } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php index 2766bf630739e4fe0562249ee82253fe661786f6..6ff97830c8e8fb95f740143b08ad521dd14322f4 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php @@ -6,26 +6,36 @@ namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Category; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Serialize\Serializer\Json; class ChildrenUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase { /** @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator */ - protected $childrenUrlRewriteGenerator; + private $childrenUrlRewriteGenerator; /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $category; + private $category; /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $childrenCategoriesProvider; + private $childrenCategoriesProvider; /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $categoryUrlRewriteGeneratorFactory; + private $categoryUrlRewriteGeneratorFactory; /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $categoryUrlRewriteGenerator; + private $categoryUrlRewriteGenerator; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $mergeDataProvider; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $serializerMock; protected function setUp() { + $this->serializerMock = $this->getMockBuilder(Json::class) + ->disableOriginalConstructor() + ->getMock(); $this->childrenCategoriesProvider = $this->getMockBuilder( \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider::class )->disableOriginalConstructor()->getMock(); @@ -37,18 +47,29 @@ class ChildrenUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase $this->categoryUrlRewriteGenerator = $this->getMockBuilder( \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator::class )->disableOriginalConstructor()->getMock(); + $mergeDataProviderFactory = $this->getMock( + \Magento\UrlRewrite\Model\MergeDataProviderFactory::class, + ['create'], + [], + '', + false + ); + $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider; + $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider); + $this->childrenUrlRewriteGenerator = (new ObjectManager($this))->getObject( \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator::class, [ 'childrenCategoriesProvider' => $this->childrenCategoriesProvider, - 'categoryUrlRewriteGeneratorFactory' => $this->categoryUrlRewriteGeneratorFactory + 'categoryUrlRewriteGeneratorFactory' => $this->categoryUrlRewriteGeneratorFactory, + 'mergeDataProviderFactory' => $mergeDataProviderFactory ] ); } public function testNoChildrenCategories() { - $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, false) + $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, true) ->will($this->returnValue([])); $this->assertEquals([], $this->childrenUrlRewriteGenerator->generate('store_id', $this->category)); @@ -64,18 +85,28 @@ class ChildrenUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase $childCategory->expects($this->once())->method('setStoreId')->with($storeId); $childCategory->expects($this->once())->method('setData') ->with('save_rewrites_history', $saveRewritesHistory); - $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, false) + $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, true) ->will($this->returnValue([$childCategory])); $this->category->expects($this->any())->method('getData')->with('save_rewrites_history') ->will($this->returnValue($saveRewritesHistory)); $this->categoryUrlRewriteGeneratorFactory->expects($this->once())->method('create') ->will($this->returnValue($this->categoryUrlRewriteGenerator)); - $this->categoryUrlRewriteGenerator->expects($this->once())->method('generate')->with($childCategory) - ->will($this->returnValue([['url-1', 'url-2']])); + $url1 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializerMock); + $url1->setRequestPath('category-1') + ->setStoreId(1); + $url2 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializerMock); + $url2->setRequestPath('category-2') + ->setStoreId(2); + $url3 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializerMock); + $url3->setRequestPath('category-1') + ->setStoreId(1); + $this->categoryUrlRewriteGenerator->expects($this->once())->method('generate') + ->with($childCategory, false, 1) + ->will($this->returnValue([$url1, $url2, $url3])); $this->assertEquals( - [['url-1', 'url-2']], - $this->childrenUrlRewriteGenerator->generate($storeId, $this->category) + ['category-1_1' => $url1, 'category-2_2' => $url2], + $this->childrenUrlRewriteGenerator->generate($storeId, $this->category, 1) ); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php index 2f9a9463646f1f54382720c649e22565eec62683..483df828fb0446a046ca649de83f0511027d531c 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php @@ -14,25 +14,25 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase { /** @var \Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator */ - protected $currentUrlRewritesRegenerator; - - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $filter; - - /** @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlFinder; + private $currentUrlRewritesRegenerator; /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */ - protected $categoryUrlPathGenerator; + private $categoryUrlPathGenerator; /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */ - protected $category; + private $category; /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlRewriteFactory; + private $urlRewriteFactory; /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlRewrite; + private $urlRewrite; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $mergeDataProvider; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $urlRewriteFinder; protected function setUp() { @@ -43,41 +43,50 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor()->getMock(); $this->category = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) ->disableOriginalConstructor()->getMock(); - $this->filter = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\Filter::class) - ->disableOriginalConstructor()->getMock(); - $this->filter->expects($this->any())->method('setStoreId')->will($this->returnSelf()); - $this->filter->expects($this->any())->method('setEntityId')->will($this->returnSelf()); - $this->urlFinder = $this->getMockBuilder(\Magento\UrlRewrite\Model\UrlFinderInterface::class) - ->disableOriginalConstructor()->getMock(); - $this->categoryUrlPathGenerator = $this->getMockBuilder( + $this->categoryUrlPathGenerator = $this->getMockBuilder( \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator::class )->disableOriginalConstructor()->getMock(); - $this->currentUrlRewritesRegenerator = (new ObjectManager($this))->getObject( + $this->urlRewriteFinder = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder::class) + ->disableOriginalConstructor()->getMock(); + $this->urlRewriteFactory->expects($this->once())->method('create') + ->willReturn($this->urlRewrite); + $mergeDataProviderFactory = $this->getMock( + \Magento\UrlRewrite\Model\MergeDataProviderFactory::class, + ['create'], + [], + '', + false + ); + $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider; + $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider); + + $this->currentUrlRewritesRegenerator = (new ObjectManager($this))->getObject( \Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator::class, [ - 'urlFinder' => $this->urlFinder, 'categoryUrlPathGenerator' => $this->categoryUrlPathGenerator, - 'urlRewriteFactory' => $this->urlRewriteFactory + 'urlRewriteFactory' => $this->urlRewriteFactory, + 'mergeDataProviderFactory' => $mergeDataProviderFactory, + 'urlRewriteFinder' => $this->urlRewriteFinder ] ); } public function testIsAutogeneratedWithoutSaveRewriteHistory() { - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will($this->returnValue($this->getCurrentRewritesMocks([[UrlRewrite::IS_AUTOGENERATED => 1]]))); $this->category->expects($this->once())->method('getData')->with('save_rewrites_history') ->will($this->returnValue(false)); $this->assertEquals( [], - $this->currentUrlRewritesRegenerator->generate('store_id', $this->category) + $this->currentUrlRewritesRegenerator->generate('store_id', $this->category, $this->category) ); } public function testSkipGenerationForAutogenerated() { - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will( $this->returnValue( $this->getCurrentRewritesMocks( @@ -94,7 +103,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $this->assertEquals( [], - $this->currentUrlRewritesRegenerator->generate('store_id', $this->category) + $this->currentUrlRewritesRegenerator->generate('store_id', $this->category, $this->category) ); } @@ -104,7 +113,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $targetPath = 'some-path.html'; $storeId = 2; $categoryId = 12; - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will( $this->returnValue( $this->getCurrentRewritesMocks( @@ -120,23 +129,24 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase ) ) ); - $this->category->expects($this->any())->method('getId')->will($this->returnValue($categoryId)); + + $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue($categoryId)); $this->category->expects($this->once())->method('getData')->with('save_rewrites_history') ->will($this->returnValue(true)); $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix') ->will($this->returnValue($targetPath)); - $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, OptionProvider::PERMANENT); + $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, OptionProvider::PERMANENT, 0); $this->assertEquals( ['autogenerated.html_2' => $this->urlRewrite], - $this->currentUrlRewritesRegenerator->generate($storeId, $this->category) + $this->currentUrlRewritesRegenerator->generate($storeId, $this->category, $this->category) ); } public function testSkipGenerationForCustom() { - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will( $this->returnValue( $this->getCurrentRewritesMocks( @@ -155,7 +165,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $this->assertEquals( [], - $this->currentUrlRewritesRegenerator->generate('store_id', $this->category) + $this->currentUrlRewritesRegenerator->generate('store_id', $this->category, $this->category) ); } @@ -166,7 +176,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $requestPath = 'generate-for-custom-without-redirect-type.html'; $targetPath = 'custom-target-path.html'; $description = 'description'; - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will( $this->returnValue( $this->getCurrentRewritesMocks( @@ -184,16 +194,14 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase ) ); $this->categoryUrlPathGenerator->expects($this->never())->method('getUrlPathWithSuffix'); - $this->category->expects($this->any())->method('getId')->will($this->returnValue($categoryId)); + $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue($categoryId)); $this->urlRewrite->expects($this->once())->method('setDescription')->with($description) ->will($this->returnSelf()); - $this->urlRewriteFactory->expects($this->once())->method('create') - ->willReturn($this->urlRewrite); - $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 0); + $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 0, 0); $this->assertEquals( ['generate-for-custom-without-redirect-type.html_12' => $this->urlRewrite], - $this->currentUrlRewritesRegenerator->generate($storeId, $this->category) + $this->currentUrlRewritesRegenerator->generate($storeId, $this->category, $this->category) ); } @@ -204,7 +212,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $requestPath = 'generate-for-custom-without-redirect-type.html'; $targetPath = 'generated-target-path.html'; $description = 'description'; - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will( $this->returnValue( $this->getCurrentRewritesMocks( @@ -223,16 +231,14 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase ); $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPathWithSuffix') ->will($this->returnValue($targetPath)); - $this->category->expects($this->any())->method('getId')->will($this->returnValue($categoryId)); + $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue($categoryId)); $this->urlRewrite->expects($this->once())->method('setDescription')->with($description) ->will($this->returnSelf()); - $this->urlRewriteFactory->expects($this->once())->method('create') - ->willReturn($this->urlRewrite); - $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 'code'); + $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 'code', 0); $this->assertEquals( ['generate-for-custom-without-redirect-type.html_12' => $this->urlRewrite], - $this->currentUrlRewritesRegenerator->generate($storeId, $this->category) + $this->currentUrlRewritesRegenerator->generate($storeId, $this->category, $this->category) ); } @@ -263,9 +269,16 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase * @param mixed $requestPath * @param mixed $targetPath * @param mixed $redirectType + * @param int $isAutoGenerated */ - protected function prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, $redirectType) - { + protected function prepareUrlRewriteMock( + $storeId, + $categoryId, + $requestPath, + $targetPath, + $redirectType, + $isAutoGenerated + ) { $this->urlRewrite->expects($this->any())->method('setStoreId')->with($storeId) ->will($this->returnSelf()); $this->urlRewrite->expects($this->any())->method('setEntityId')->with($categoryId) @@ -276,11 +289,14 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase ->will($this->returnSelf()); $this->urlRewrite->expects($this->any())->method('setTargetPath')->with($targetPath) ->will($this->returnSelf()); - $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with(0) + $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with($isAutoGenerated) ->will($this->returnSelf()); $this->urlRewrite->expects($this->any())->method('setRedirectType')->with($redirectType) ->will($this->returnSelf()); $this->urlRewrite->expects($this->any())->method('setMetadata')->with([])->will($this->returnSelf()); + $this->urlRewrite->expects($this->any())->method('getTargetPath')->willReturn($targetPath); + $this->urlRewrite->expects($this->any())->method('getRequestPath')->willReturn($requestPath); + $this->urlRewrite->expects($this->any())->method('getStoreId')->willReturn($storeId); $this->urlRewriteFactory->expects($this->any())->method('create')->will($this->returnValue($this->urlRewrite)); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/RemoveTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/RemoveTest.php index 75b0509e0245db845bf3c82f6e6502affbcdb898..a2cd880e85acca6c60df9078b54df53f28d5c3ca 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/RemoveTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/RemoveTest.php @@ -39,6 +39,9 @@ class RemoveTest extends \PHPUnit_Framework_TestCase */ private $objectMock; + /** @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject */ + private $serializerMock; + protected function setUp() { $this->objectManager = new ObjectManager($this); @@ -52,6 +55,7 @@ class RemoveTest extends \PHPUnit_Framework_TestCase $this->objectMock = $this->getMockBuilder(Category::class) ->disableOriginalConstructor() ->getMock(); + $this->serializerMock = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class, [], [], '', false); } public function testAroundDelete() @@ -64,7 +68,8 @@ class RemoveTest extends \PHPUnit_Framework_TestCase CategoryRemovePlugin::class, [ 'urlPersist' => $this->urlPersistMock, - 'childrenCategoriesProvider' => $this->childrenCategoriesProviderMock + 'childrenCategoriesProvider' => $this->childrenCategoriesProviderMock, + 'serializer' => $this->serializerMock ] ); $this->childrenCategoriesProviderMock->expects($this->once()) @@ -76,6 +81,9 @@ class RemoveTest extends \PHPUnit_Framework_TestCase ->willReturn(1); $this->urlPersistMock->expects($this->exactly(2)) ->method('deleteByData'); + $this->serializerMock->expects($this->once()) + ->method('serialize') + ->with(['category_id' => 1]); $this->assertSame( $this->subjectMock, $plugin->aroundDelete($this->subjectMock, $proceed, $this->objectMock) diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php index aad5e9ffec89811e2f21fca5716a9d9f0e2f0c40..927c451e6522865454220c4401ddbcc21a3f4b8f 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php @@ -6,7 +6,6 @@ namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Category\Plugin; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\CatalogUrlRewrite\Model\Category\ProductFactory; use Magento\UrlRewrite\Model\StorageInterface; use Magento\CatalogUrlRewrite\Model\Category\Plugin\Storage as CategoryStoragePlugin; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; @@ -24,11 +23,6 @@ class StorageTest extends \PHPUnit_Framework_TestCase */ private $plugin; - /** - * @var ProductFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $productFactory; - /** * @var UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -56,10 +50,6 @@ class StorageTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->productFactory = $this->getMockBuilder(ProductFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); $this->storage = $this->getMockBuilder(StorageInterface::class) ->getMockForAbstractClass(); $this->urlFinder = $this->getMockBuilder(UrlFinderInterface::class) @@ -78,12 +68,15 @@ class StorageTest extends \PHPUnit_Framework_TestCase $this->plugin = (new ObjectManager($this))->getObject( CategoryStoragePlugin::class, [ - 'productFactory' => $this->productFactory, - 'urlFinder' => $this->urlFinder + 'urlFinder' => $this->urlFinder, + 'productResource' => $this->productResourceModel ] ); } + /** + * test AfterReplace method + */ public function testAfterReplace() { $this->urlRewrite->expects(static::any())->method('getMetadata')->willReturn(['category_id' => '5']); @@ -97,10 +90,20 @@ class StorageTest extends \PHPUnit_Framework_TestCase $this->urlFinder->expects(static::once())->method('findAllByData')->willReturn([$this->urlRewrite]); - $this->productFactory->expects(static::once())->method('create')->willReturn($this->product); - $this->product->expects(static::once())->method('getResource')->willReturn($this->productResourceModel); $this->productResourceModel->expects(static::once())->method('saveMultiple')->willReturnSelf(); $this->plugin->afterReplace($this->storage, null, $productUrls); } + + /** + * test BeforeDeleteByData method + */ + public function testBeforeDeleteByData() + { + $data = [1, 2, 3]; + $this->productResourceModel->expects(static::once()) + ->method('removeMultipleByProductCategory') + ->with($data)->willReturnSelf(); + $this->plugin->beforeDeleteByData($this->storage, $data); + } } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php index 035112a592916060e637ad284ea4845ebbfb5d66..32f310c33b8cbcf27cda92c279e14f39e39394e2 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php @@ -12,12 +12,11 @@ use Magento\CatalogUrlRewrite\Model\ProductScopeRewriteGenerator; /** * Class CategoryBasedProductRewriteGeneratorTest - * @package Magento\CatalogUrlRewrite\Test\Unit\Model */ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCase { /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ProductScopeRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject */ private $productScopeRewriteGeneratorMock; @@ -46,6 +45,7 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa ->disableOriginalConstructor() ->getMock(); $storeId = 1; + $categoryId = 1; $urls = ['dummy-url.html']; $productMock->expects($this->once()) @@ -60,10 +60,10 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa ->willReturn(true); $this->productScopeRewriteGeneratorMock->expects($this->once()) ->method('generateForGlobalScope') - ->with([$categoryMock], $productMock) + ->with([$categoryMock], $productMock, $categoryId) ->willReturn($urls); - $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock)); + $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock, $categoryId)); } public function testGenerationWithSpecificStore() @@ -75,6 +75,7 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa ->disableOriginalConstructor() ->getMock(); $storeId = 1; + $categoryId = 1; $urls = ['dummy-url.html']; $productMock->expects($this->once()) @@ -89,9 +90,9 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa ->willReturn(false); $this->productScopeRewriteGeneratorMock->expects($this->once()) ->method('generateForSpecificStoreView') - ->with($storeId, [$categoryMock], $productMock) + ->with($storeId, [$categoryMock], $productMock, $categoryId) ->willReturn($urls); - $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock)); + $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock, $categoryId)); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php index d5abe69ff79777cf02067899eff0d434c76620a9..f36799f789fbdb3a9c287f4e14e1c3abab5b3c36 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php @@ -5,8 +5,7 @@ */ namespace Magento\CatalogUrlRewrite\Test\Unit\Model; -use \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; - +use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; use Magento\Catalog\Model\Category; use Magento\Store\Model\ScopeInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php index c2905eb9b425f243126547bda3791bf570ee6977..cfc6243525f42240136394befa6b3c5dfe4ea49f 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php @@ -8,52 +8,87 @@ namespace Magento\CatalogUrlRewrite\Test\Unit\Model; -use Magento\Catalog\Model\Category; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +/** + * Class CategoryUrlRewriteGeneratorTest + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase { /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $canonicalUrlRewriteGenerator; + private $canonicalUrlRewriteGenerator; /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $currentUrlRewritesRegenerator; + private $currentUrlRewritesRegenerator; /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $childrenUrlRewriteGenerator; + private $childrenUrlRewriteGenerator; /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator */ - protected $categoryUrlRewriteGenerator; + private $categoryUrlRewriteGenerator; /** @var \Magento\CatalogUrlRewrite\Service\V1\StoreViewService|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeViewService; + private $storeViewService; /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */ - protected $category; + private $category; /** @var \Magento\Catalog\Api\CategoryRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $categoryRepository; + private $categoryRepository; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $mergeDataProvider; + + /** @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject */ + protected $serializer; /** * Test method */ protected function setUp() { - $this->currentUrlRewritesRegenerator = $this->getMockBuilder( + $this->serializer = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class, [], [], '', false); + $this->serializer->expects($this->any()) + ->method('serialize') + ->willReturnCallback( + function ($value) { + return json_encode($value); + } + ); + $this->serializer->expects($this->any()) + ->method('unserialize') + ->willReturnCallback( + function ($value) { + return json_decode($value, true); + } + ); + + $this->currentUrlRewritesRegenerator = $this->getMockBuilder( \Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator::class )->disableOriginalConstructor()->getMock(); - $this->canonicalUrlRewriteGenerator = $this->getMockBuilder( + $this->canonicalUrlRewriteGenerator = $this->getMockBuilder( \Magento\CatalogUrlRewrite\Model\Category\CanonicalUrlRewriteGenerator::class )->disableOriginalConstructor()->getMock(); - $this->childrenUrlRewriteGenerator = $this->getMockBuilder( + $this->childrenUrlRewriteGenerator = $this->getMockBuilder( \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator::class )->disableOriginalConstructor()->getMock(); $this->storeViewService = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Service\V1\StoreViewService::class) ->disableOriginalConstructor()->getMock(); $this->category = $this->getMock(\Magento\Catalog\Model\Category::class, [], [], '', false); $this->categoryRepository = $this->getMock(\Magento\Catalog\Api\CategoryRepositoryInterface::class); + $mergeDataProviderFactory = $this->getMock( + \Magento\UrlRewrite\Model\MergeDataProviderFactory::class, + ['create'], + [], + '', + false + ); + $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider; + $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider); - $this->categoryUrlRewriteGenerator = (new ObjectManager($this))->getObject( + $this->categoryUrlRewriteGenerator = (new ObjectManager($this))->getObject( \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator::class, [ 'canonicalUrlRewriteGenerator' => $this->canonicalUrlRewriteGenerator, @@ -61,6 +96,7 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase 'currentUrlRewritesRegenerator' => $this->currentUrlRewritesRegenerator, 'storeViewService' => $this->storeViewService, 'categoryRepository' => $this->categoryRepository, + 'mergeDataProviderFactory' => $mergeDataProviderFactory ] ); } @@ -70,26 +106,32 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase */ public function testGenerationForGlobalScope() { + $categoryId = 1; $this->category->expects($this->any())->method('getStoreId')->will($this->returnValue(null)); $this->category->expects($this->any())->method('getStoreIds')->will($this->returnValue([1])); $this->storeViewService->expects($this->once())->method('doesEntityHaveOverriddenUrlKeyForStore') ->will($this->returnValue(false)); - $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite(); - $canonical->setTargetPath('category-1') + $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer); + $canonical->setRequestPath('category-1') ->setStoreId(1); $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate') - ->will($this->returnValue([$canonical])); - $children = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite(); - $children->setTargetPath('category-2') + ->will($this->returnValue(['category-1' => $canonical])); + $children1 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer); + $children1->setRequestPath('category-2') + ->setStoreId(2); + $children2 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer); + $children2->setRequestPath('category-22') ->setStoreId(2); $this->childrenUrlRewriteGenerator->expects($this->any())->method('generate') - ->will($this->returnValue([$children])); - $current = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite(); - $current->setTargetPath('category-3') + ->with(1, $this->category, $categoryId) + ->will($this->returnValue(['category-2' => $children1, 'category-1' => $children2])); + $current = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer); + $current->setRequestPath('category-3') ->setStoreId(3); $this->currentUrlRewritesRegenerator->expects($this->any())->method('generate') - ->will($this->returnValue([$current])); - $categoryForSpecificStore = $this->getMock( + ->with(1, $this->category, $categoryId) + ->will($this->returnValue(['category-3' => $current])); + $categoryForSpecificStore = $this->getMock( \Magento\Catalog\Model\Category::class, ['getUrlKey', 'getUrlPath'], [], @@ -99,8 +141,13 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase $this->categoryRepository->expects($this->once())->method('get')->willReturn($categoryForSpecificStore); $this->assertEquals( - [$canonical, $children, $current], - $this->categoryUrlRewriteGenerator->generate($this->category) + [ + 'category-1_1' => $canonical, + 'category-2_2' => $children1, + 'category-22_2' => $children2, + 'category-3_3' => $current + ], + $this->categoryUrlRewriteGenerator->generate($this->category, false, $categoryId) ); } @@ -111,8 +158,8 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase { $this->category->expects($this->any())->method('getStoreId')->will($this->returnValue(1)); $this->category->expects($this->never())->method('getStoreIds'); - $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite(); - $canonical->setTargetPath('category-1') + $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer); + $canonical->setRequestPath('category-1') ->setStoreId(1); $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate') ->will($this->returnValue([$canonical])); @@ -121,7 +168,10 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase $this->currentUrlRewritesRegenerator->expects($this->any())->method('generate') ->will($this->returnValue([])); - $this->assertEquals([$canonical], $this->categoryUrlRewriteGenerator->generate($this->category)); + $this->assertEquals( + ['category-1_1' => $canonical], + $this->categoryUrlRewriteGenerator->generate($this->category, 1) + ); } /** @@ -135,4 +185,18 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase $this->assertEquals([], $this->categoryUrlRewriteGenerator->generate($this->category)); } + + /** + * Test method + */ + public function testSkipGenerationForGlobalScopeWithCategory() + { + $this->category->expects($this->any())->method('getStoreIds')->will($this->returnValue([1, 2])); + $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue(1)); + $this->category->expects($this->any())->method('getStoreId')->will($this->returnValue(false)); + $this->storeViewService->expects($this->exactly(2))->method('doesEntityHaveOverriddenUrlKeyForStore') + ->will($this->returnValue(true)); + + $this->assertEquals([], $this->categoryUrlRewriteGenerator->generate($this->category, false, 1)); + } } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryHashMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryHashMapTest.php new file mode 100644 index 0000000000000000000000000000000000000000..52d65710fed69a0902f28aacb25a5183fa28e833 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryHashMapTest.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map; + +use Magento\Catalog\Model\ResourceModel\CategoryFactory; +use Magento\Catalog\Model\ResourceModel\Category; +use Magento\Framework\DB\Select; +use Magento\Catalog\Model\CategoryRepository; +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\DB\Adapter\AdapterInterface; + +/** + * Class DataCategoryHashMapTest + */ +class DataCategoryHashMapTest extends \PHPUnit_Framework_TestCase +{ + /** @var CategoryRepository|\PHPUnit_Framework_MockObject_MockObject */ + private $categoryRepository; + + /** @var CategoryResourceFactory|\PHPUnit_Framework_MockObject_MockObject */ + private $categoryResourceFactory; + + /** @var Category|\PHPUnit_Framework_MockObject_MockObject */ + private $categoryResource; + + /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */ + private $model; + + protected function setUp() + { + $this->categoryRepository = $this->getMock(CategoryRepository::class, [], [], '', false); + $this->categoryResourceFactory = $this->getMock(CategoryFactory::class, ['create'], [], '', false); + $this->categoryResource = $this->getMock( + Category::class, + ['getConnection', 'getEntityTable'], + [], + '', + false + ); + + $this->categoryResourceFactory->expects($this->any()) + ->method('create') + ->willReturn($this->categoryResource); + + $this->model = (new ObjectManager($this))->getObject( + DataCategoryHashMap::class, + [ + 'categoryRepository' => $this->categoryRepository, + 'categoryResourceFactory' => $this->categoryResourceFactory + ] + ); + } + + /** + * Tests getAllData, getData and resetData functionality + */ + public function testGetAllData() + { + $categoryIds = ['1' => [1, 2, 3], '2' => [2, 3], '3' => 3]; + $categoryIdsOther = ['2' => [2, 3, 4]]; + + $categoryMock = $this->getMock(CategoryInterface::class, [], [], '', false); + $connectionAdapterMock = $this->getMock(AdapterInterface::class); + $selectMock = $this->getMock(Select::class, [], [], '', false); + + $this->categoryRepository->expects($this->any()) + ->method('get') + ->willReturn($categoryMock); + $categoryMock->expects($this->any()) + ->method('getResource') + ->willReturn($this->categoryResource); + $this->categoryResource->expects($this->any()) + ->method('getConnection') + ->willReturn($connectionAdapterMock); + $this->categoryResource->expects($this->any()) + ->method('getEntityTable') + ->willReturn('category_entity'); + $connectionAdapterMock->expects($this->any()) + ->method('select') + ->willReturn($selectMock); + $selectMock->expects($this->any()) + ->method('from') + ->willReturnSelf(); + $selectMock->expects($this->any()) + ->method('where') + ->willReturnSelf(); + $connectionAdapterMock->expects($this->any()) + ->method('fetchCol') + ->willReturnOnConsecutiveCalls($categoryIds, $categoryIdsOther, $categoryIds); + + $this->assertEquals($categoryIds, $this->model->getAllData(1)); + $this->assertEquals($categoryIds[2], $this->model->getData(1, 2)); + $this->assertEquals($categoryIdsOther, $this->model->getAllData(2)); + $this->assertEquals($categoryIdsOther[2], $this->model->getData(2, 2)); + $this->model->resetData(1); + $this->assertEquals($categoryIds[2], $this->model->getData(1, 2)); + $this->assertEquals($categoryIds, $this->model->getAllData(1)); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUrlRewriteDatabaseMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUrlRewriteDatabaseMapTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7891c56be1368b7917bfb01e33a8a84ef79eba6b --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUrlRewriteDatabaseMapTest.php @@ -0,0 +1,125 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map; + +use Magento\Framework\DB\Select; +use Magento\CatalogUrlRewrite\Model\Map\HashMapPool; +use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUsedInProductsHashMap; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\TemporaryTableService; + +/** + * Class DataCategoryUrlRewriteDatabaseMapTest + */ +class DataCategoryUrlRewriteDatabaseMapTest extends \PHPUnit_Framework_TestCase +{ + /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */ + private $hashMapPoolMock; + + /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */ + private $dataCategoryMapMock; + + /** @var DataCategoryUsedInProductsHashMap|\PHPUnit_Framework_MockObject_MockObject */ + private $dataCategoryUsedInProductsMapMock; + + /** @var TemporaryTableService|\PHPUnit_Framework_MockObject_MockObject */ + private $temporaryTableServiceMock; + + /** @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject */ + private $connectionMock; + + /** @var DataCategoryUrlRewriteDatabaseMap|\PHPUnit_Framework_MockObject_MockObject */ + private $model; + + protected function setUp() + { + $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false); + $this->dataCategoryMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false); + $this->dataCategoryUsedInProductsMapMock = $this->getMock( + DataCategoryUsedInProductsHashMap::class, + [], + [], + '', + false + ); + $this->temporaryTableServiceMock = $this->getMock(TemporaryTableService::class, [], [], '', false); + $this->connectionMock = $this->getMock(ResourceConnection::class, [], [], '', false); + + $this->hashMapPoolMock->expects($this->any()) + ->method('getDataMap') + ->willReturnOnConsecutiveCalls($this->dataCategoryUsedInProductsMapMock, $this->dataCategoryMapMock); + + $this->model = (new ObjectManager($this))->getObject( + DataCategoryUrlRewriteDatabaseMap::class, + [ + 'connection' => $this->connectionMock, + 'hashMapPool' => $this->hashMapPoolMock, + 'temporaryTableService' => $this->temporaryTableServiceMock + ] + ); + } + + /** + * Tests getAllData, getData and resetData functionality + */ + public function testGetAllData() + { + $productStoreIds = [ + '1' => ['store_id' => 1, 'category_id' => 1], + '2' => ['store_id' => 2, 'category_id' => 1], + '3' => ['store_id' => 3, 'category_id' => 1], + '4' => ['store_id' => 1, 'category_id' => 2], + '5' => ['store_id' => 2, 'category_id' => 2], + ]; + + $connectionMock = $this->getMock(AdapterInterface::class); + $selectMock = $this->getMock(Select::class, [], [], '', false); + + $this->connectionMock->expects($this->any()) + ->method('getConnection') + ->willReturn($connectionMock); + $connectionMock->expects($this->any()) + ->method('select') + ->willReturn($selectMock); + $connectionMock->expects($this->any()) + ->method('fetchAll') + ->willReturn($productStoreIds[3]); + $selectMock->expects($this->any()) + ->method('from') + ->willReturnSelf(); + $selectMock->expects($this->any()) + ->method('joinInner') + ->willReturnSelf(); + $selectMock->expects($this->any()) + ->method('where') + ->willReturnSelf(); + $this->dataCategoryMapMock->expects($this->once()) + ->method('getAllData') + ->willReturn([]); + $this->dataCategoryUsedInProductsMapMock->expects($this->once()) + ->method('getAllData') + ->willReturn([]); + $this->temporaryTableServiceMock->expects($this->any()) + ->method('createFromSelect') + ->withConsecutive( + $selectMock, + $connectionMock, + [ + 'PRIMARY' => ['url_rewrite_id'], + 'HASHKEY_ENTITY_STORE' => ['hash_key'], + 'ENTITY_STORE' => ['entity_id', 'store_id'] + ] + ) + ->willReturn('tempTableName'); + + $this->assertEquals($productStoreIds[3], $this->model->getData(1, '3_1')); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUsedInProductsHashMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUsedInProductsHashMapTest.php new file mode 100644 index 0000000000000000000000000000000000000000..74353d3a924f7687eb238399d38380bffcab36c5 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUsedInProductsHashMapTest.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map; + +use Magento\Framework\DB\Select; +use Magento\CatalogUrlRewrite\Model\Map\HashMapPool; +use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUsedInProductsHashMap; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\App\ResourceConnection; + +/** + * Class DataCategoryUsedInProductsHashMapTest + */ +class DataCategoryUsedInProductsHashMapTest extends \PHPUnit_Framework_TestCase +{ + /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */ + private $hashMapPoolMock; + + /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */ + private $dataCategoryMapMock; + + /** @var DataProductHashMap|\PHPUnit_Framework_MockObject_MockObject */ + private $dataProductMapMock; + + /** @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject */ + private $connectionMock; + + /** @var DataCategoryUsedInProductsHashMap|\PHPUnit_Framework_MockObject_MockObject */ + private $model; + + protected function setUp() + { + $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false); + $this->dataCategoryMapMock = $this->getMock(DataCategoryHashMap::class, [], [], '', false); + $this->dataProductMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false); + $this->connectionMock = $this->getMock(ResourceConnection::class, [], [], '', false); + + $this->hashMapPoolMock->expects($this->any()) + ->method('getDataMap') + ->willReturnOnConsecutiveCalls( + $this->dataProductMapMock, + $this->dataCategoryMapMock, + $this->dataProductMapMock, + $this->dataCategoryMapMock, + $this->dataProductMapMock, + $this->dataCategoryMapMock + ); + + $this->model = (new ObjectManager($this))->getObject( + DataCategoryUsedInProductsHashMap::class, + [ + 'connection' => $this->connectionMock, + 'hashMapPool' => $this->hashMapPoolMock + ] + ); + } + + /** + * Tests getAllData, getData and resetData functionality + */ + public function testGetAllData() + { + $categoryIds = ['1' => [1, 2, 3], '2' => [2, 3], '3' => 3]; + $categoryIdsOther = ['2' => [2, 3, 4]]; + + $connectionMock = $this->getMock(AdapterInterface::class); + $selectMock = $this->getMock(Select::class, [], [], '', false); + + $this->connectionMock->expects($this->any()) + ->method('getConnection') + ->willReturn($connectionMock); + $connectionMock->expects($this->any()) + ->method('select') + ->willReturn($selectMock); + $connectionMock->expects($this->any()) + ->method('fetchCol') + ->willReturnOnConsecutiveCalls($categoryIds, $categoryIdsOther, $categoryIds); + $selectMock->expects($this->any()) + ->method('from') + ->willReturnSelf(); + $selectMock->expects($this->any()) + ->method('joinInner') + ->willReturnSelf(); + $selectMock->expects($this->any()) + ->method('where') + ->willReturnSelf(); + $this->hashMapPoolMock->expects($this->at(4)) + ->method('resetMap') + ->with(DataProductHashMap::class, 1); + $this->hashMapPoolMock->expects($this->at(5)) + ->method('resetMap') + ->with(DataCategoryHashMap::class, 1); + + $this->assertEquals($categoryIds, $this->model->getAllData(1)); + $this->assertEquals($categoryIds[2], $this->model->getData(1, 2)); + $this->assertEquals($categoryIdsOther, $this->model->getAllData(2)); + $this->assertEquals($categoryIdsOther[2], $this->model->getData(2, 2)); + $this->model->resetData(1); + $this->assertEquals($categoryIds[2], $this->model->getData(1, 2)); + $this->assertEquals($categoryIds, $this->model->getAllData(1)); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductHashMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductHashMapTest.php new file mode 100644 index 0000000000000000000000000000000000000000..53e4d225977698c0a3b3bd4afe7809a58f3623bd --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductHashMapTest.php @@ -0,0 +1,121 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map; + +use Magento\Catalog\Model\ResourceModel\Category\Collection; +use Magento\Catalog\Model\ResourceModel\Category as CategoryResource; +use Magento\Framework\DB\Select; +use Magento\Catalog\Model\ProductRepository; +use Magento\CatalogUrlRewrite\Model\Map\HashMapPool; +use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; + +/** + * Class DataProductHashMapTest + */ +class DataProductHashMapTest extends \PHPUnit_Framework_TestCase +{ + /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */ + private $hashMapPoolMock; + + /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */ + private $dataCategoryMapMock; + + /** + * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $collectionFactoryMock; + + /** + * @var ProductCollection|\PHPUnit_Framework_MockObject_MockObject + */ + private $productCollectionMock; + + /** @var DataProductHashMap|\PHPUnit_Framework_MockObject_MockObject */ + private $model; + + protected function setUp() + { + $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false); + $this->dataCategoryMapMock = $this->getMock(DataCategoryHashMap::class, [], [], '', false); + $this->collectionFactoryMock = $this->getMock(CollectionFactory::class, ['create'], [], '', false); + $this->productCollectionMock = $this->getMock( + ProductCollection::class, + ['getSelect', 'getConnection', 'getAllIds'], + [], + '', + false + ); + + $this->collectionFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($this->productCollectionMock); + + $this->hashMapPoolMock->expects($this->any()) + ->method('getDataMap') + ->willReturn($this->dataCategoryMapMock); + + $this->model = (new ObjectManager($this))->getObject( + DataProductHashMap::class, + [ + 'collectionFactory' => $this->collectionFactoryMock, + 'hashMapPool' => $this->hashMapPoolMock + ] + ); + } + + /** + * Tests getAllData, getData and resetData functionality + */ + public function testGetAllData() + { + $productIds = ['1' => [1, 2, 3], '2' => [2, 3], '3' => 3]; + $productIdsOther = ['2' => [2, 3, 4]]; + + $connectionMock = $this->getMock(AdapterInterface::class); + $selectMock = $this->getMock(Select::class, [], [], '', false); + + $this->productCollectionMock->expects($this->exactly(3)) + ->method('getAllIds') + ->willReturnOnConsecutiveCalls($productIds, $productIdsOther, $productIds); + $this->productCollectionMock->expects($this->any()) + ->method('getConnection') + ->willReturn($connectionMock); + $connectionMock->expects($this->any()) + ->method('getTableName') + ->willReturn($this->returnValue($this->returnArgument(0))); + $this->productCollectionMock->expects($this->any()) + ->method('getSelect') + ->willReturn($selectMock); + $selectMock->expects($this->any()) + ->method('from') + ->willReturnSelf(); + $selectMock->expects($this->any()) + ->method('joinInner') + ->willReturnSelf(); + $selectMock->expects($this->any()) + ->method('where') + ->willReturnSelf(); + $this->dataCategoryMapMock->expects($this->any()) + ->method('getAllData') + ->willReturn([]); + $this->hashMapPoolMock->expects($this->any()) + ->method('resetMap') + ->with(DataCategoryHashMap::class, 1); + $this->assertEquals($productIds, $this->model->getAllData(1)); + $this->assertEquals($productIds[2], $this->model->getData(1, 2)); + $this->assertEquals($productIdsOther, $this->model->getAllData(2)); + $this->assertEquals($productIdsOther[2], $this->model->getData(2, 2)); + $this->model->resetData(1); + $this->assertEquals($productIds[2], $this->model->getData(1, 2)); + $this->assertEquals($productIds, $this->model->getAllData(1)); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductUrlRewriteDatabaseMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductUrlRewriteDatabaseMapTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d283c6d3f0410f9df87036be147f47c17ca7f212 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductUrlRewriteDatabaseMapTest.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map; + +use Magento\Framework\DB\Select; +use Magento\CatalogUrlRewrite\Model\Map\HashMapPool; +use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap; +use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\TemporaryTableService; + +/** + * Class DataProductUrlRewriteDatabaseMapTest + */ +class DataProductUrlRewriteDatabaseMapTest extends \PHPUnit_Framework_TestCase +{ + /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */ + private $hashMapPoolMock; + + /** @var DataProductHashMap|\PHPUnit_Framework_MockObject_MockObject */ + private $dataProductMapMock; + + /** @var TemporaryTableService|\PHPUnit_Framework_MockObject_MockObject */ + private $temporaryTableServiceMock; + + /** @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject */ + private $connectionMock; + + /** @var DataProductUrlRewriteDatabaseMap|\PHPUnit_Framework_MockObject_MockObject */ + private $model; + + protected function setUp() + { + $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false); + $this->dataProductMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false); + $this->temporaryTableServiceMock = $this->getMock(TemporaryTableService::class, [], [], '', false); + $this->connectionMock = $this->getMock(ResourceConnection::class, [], [], '', false); + + $this->hashMapPoolMock->expects($this->any()) + ->method('getDataMap') + ->willReturn($this->dataProductMapMock); + + $this->model = (new ObjectManager($this))->getObject( + DataProductUrlRewriteDatabaseMap::class, + [ + 'connection' => $this->connectionMock, + 'hashMapPool' => $this->hashMapPoolMock, + 'temporaryTableService' => $this->temporaryTableServiceMock + ] + ); + } + + /** + * Tests getAllData, getData and resetData functionality + */ + public function testGetAllData() + { + $productStoreIds = [ + '1' => ['store_id' => 1, 'product_id' => 1], + '2' => ['store_id' => 2, 'product_id' => 1], + '3' => ['store_id' => 3, 'product_id' => 1], + '4' => ['store_id' => 1, 'product_id' => 2], + '5' => ['store_id' => 2, 'product_id' => 2], + ]; + + $connectionMock = $this->getMock(AdapterInterface::class); + $selectMock = $this->getMock(Select::class, [], [], '', false); + + $this->connectionMock->expects($this->any()) + ->method('getConnection') + ->willReturn($connectionMock); + $connectionMock->expects($this->any()) + ->method('select') + ->willReturn($selectMock); + $connectionMock->expects($this->any()) + ->method('fetchAll') + ->willReturn($productStoreIds[3]); + $selectMock->expects($this->any()) + ->method('from') + ->willReturnSelf(); + $selectMock->expects($this->any()) + ->method('joinInner') + ->willReturnSelf(); + $selectMock->expects($this->any()) + ->method('where') + ->willReturnSelf(); + + $this->dataProductMapMock->expects($this->any()) + ->method('getAllData') + ->willReturn([]); + + $this->temporaryTableServiceMock->expects($this->any()) + ->method('createFromSelect') + ->withConsecutive( + $selectMock, + $connectionMock, + [ + 'PRIMARY' => ['url_rewrite_id'], + 'HASHKEY_ENTITY_STORE' => ['hash_key'], + 'ENTITY_STORE' => ['entity_id', 'store_id'] + ] + ) + ->willReturn('tempTableName'); + + $this->assertEquals($productStoreIds[3], $this->model->getData(1, '3_1')); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/HashMapPoolTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/HashMapPoolTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b9efdb01f2e1dba3d8b0cc3385181fa382baa52e --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/HashMapPoolTest.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\CatalogUrlRewrite\Model\Map\HashMapPool; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap; +use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUsedInProductsHashMap; +use Magento\Framework\ObjectManagerInterface; + +/** + * Class HashMapPoolTest + */ +class HashMapPoolTest extends \PHPUnit_Framework_TestCase +{ + /** @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $objectManagerMock; + + /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */ + private $model; + + protected function setUp() + { + $this->objectManagerMock = $this->getMock(ObjectManagerInterface::class); + + $this->model = (new ObjectManager($this))->getObject( + HashMapPool::class, + [ + 'objectManager' => $this->objectManagerMock, + ] + ); + } + + /** + * Tests getDataMap + */ + public function testGetDataMap() + { + $dataCategoryMapMock = $this->getMock(DataCategoryHashMap::class, [], [], '', false); + $dataProductMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false); + $dataProductMapMockOtherCategory = $this->getMock(DataCategoryUsedInProductsHashMap::class, [], [], '', false); + + $this->objectManagerMock->expects($this->any()) + ->method('create') + ->willReturnMap( + [ + [ + DataCategoryHashMap::class, + ['category' => 1], + $dataCategoryMapMock + ], + [ + DataProductHashMap::class, + ['category' => 1], + $dataProductMapMock + ], + [ + DataCategoryUsedInProductsHashMap::class, + ['category' => 2], + $dataProductMapMockOtherCategory + ] + ] + ); + $this->assertSame($dataCategoryMapMock, $this->model->getDataMap(DataCategoryHashMap::class, 1)); + $this->assertSame($dataProductMapMock, $this->model->getDataMap(DataProductHashMap::class, 1)); + $this->assertSame( + $dataProductMapMockOtherCategory, + $this->model->getDataMap(DataCategoryUsedInProductsHashMap::class, 2) + ); + } + + /** + * Tests getDataMap with exception + */ + public function testGetDataMapException() + { + $nonInterface = $this->getMock(HashMapPool::class, [], [], '', false); + + $this->objectManagerMock->expects($this->any()) + ->method('create') + ->willReturn($nonInterface); + $this->setExpectedException(\InvalidArgumentException::class); + $this->model->getDataMap(HashMapPool::class, 1); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/UrlRewriteFinderTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/UrlRewriteFinderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ca5581f366352a6021d01338e756451db1a34d50 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/UrlRewriteFinderTest.php @@ -0,0 +1,168 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder; +use Magento\CatalogUrlRewrite\Model\Map\DatabaseMapPool; +use Magento\UrlRewrite\Model\UrlFinderInterface; +use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap; +use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap; +use Magento\Framework\Serialize\Serializer\Json; + +/** + * Class UrlRewriteFinderTest + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class UrlRewriteFinderTest extends \PHPUnit_Framework_TestCase +{ + /** @var DatabaseMapPool|\PHPUnit_Framework_MockObject_MockObject */ + private $databaseMapPoolMock; + + /** @var UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */ + private $urlRewriteFactoryMock; + + /** @var UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */ + private $urlRewritePrototypeMock; + + /** @var UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $urlFinderMock; + + /** @var UrlRewriteFinder|\PHPUnit_Framework_MockObject_MockObject */ + private $model; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $serializerMock; + + protected function setUp() + { + $this->serializerMock = $this->getMock(Json::class, [], [], '', false); + $this->databaseMapPoolMock = $this->getMock(DatabaseMapPool::class, [], [], '', false); + $this->urlFinderMock = $this->getMock(UrlFinderInterface::class); + $this->urlRewriteFactoryMock = $this->getMock(UrlRewriteFactory::class, ['create'], [], '', false); + $this->urlRewritePrototypeMock = new UrlRewrite([], $this->serializerMock); + + $this->urlRewriteFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($this->urlRewritePrototypeMock); + + $urlRewriteClassesNamesArray = [ + UrlRewriteFinder::ENTITY_TYPE_PRODUCT => DataProductUrlRewriteDatabaseMap::class, + UrlRewriteFinder::ENTITY_TYPE_CATEGORY => DataCategoryUrlRewriteDatabaseMap::class + ]; + + $this->model = (new ObjectManager($this))->getObject( + UrlRewriteFinder::class, + [ + 'databaseMapPool' => $this->databaseMapPoolMock, + 'urlFinder' => $this->urlFinderMock, + 'urlRewriteFactory' => $this->urlRewriteFactoryMock, + 'urlRewriteClassNames' => $urlRewriteClassesNamesArray + ] + ); + } + + /** + * test findAllByData using urlFinder + */ + public function testGetByIdentifiersFallback() + { + $expected = [1, 2, 3]; + $this->databaseMapPoolMock->expects($this->never()) + ->method('getDataMap'); + + $this->urlFinderMock->expects($this->exactly(7)) + ->method('findAllByData') + ->willReturn($expected); + + $this->assertEquals($expected, $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_CATEGORY)); + $this->assertEquals($expected, $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_PRODUCT)); + $this->assertEquals($expected, $this->model->findAllByData('a', 1, UrlRewriteFinder::ENTITY_TYPE_PRODUCT), 1); + $this->assertEquals($expected, $this->model->findAllByData('a', 'a', UrlRewriteFinder::ENTITY_TYPE_PRODUCT), 1); + $this->assertEquals($expected, $this->model->findAllByData(1, 'a', UrlRewriteFinder::ENTITY_TYPE_PRODUCT), 1); + $this->assertEquals($expected, $this->model->findAllByData(1, 1, 'cms', 1)); + $this->assertEquals($expected, $this->model->findAllByData(1, 1, 'cms')); + } + + /** + * test findAllByData Product URL rewrites + */ + public function testGetByIdentifiersProduct() + { + $data =[ + [ + 'url_rewrite_id' => '1', + 'entity_type' => 'product', + 'entity_id' => '3', + 'request_path' => 'request_path', + 'target_path' => 'target_path', + 'redirect_type' => 'redirect_type', + 'store_id' => '4', + 'description' => 'description', + 'is_autogenerated' => '1', + 'metadata' => '{}' + ] + ]; + + $dataProductMapMock = $this->getMock(DataProductUrlRewriteDatabaseMap::class, [], [], '', false); + $this->databaseMapPoolMock->expects($this->once()) + ->method('getDataMap') + ->with(DataProductUrlRewriteDatabaseMap::class, 1) + ->willReturn($dataProductMapMock); + + $this->urlFinderMock->expects($this->never()) + ->method('findAllByData') + ->willReturn([]); + + $dataProductMapMock->expects($this->once()) + ->method('getData') + ->willReturn($data); + + $urlRewriteResultArray = $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_PRODUCT, 1); + $this->assertEquals($data[0], $urlRewriteResultArray[0]->toArray()); + } + + /** + * test findAllByData Category URL rewrites + */ + public function testGetByIdentifiersCategory() + { + $data =[ + [ + 'url_rewrite_id' => '1', + 'entity_type' => 'category', + 'entity_id' => '3', + 'request_path' => 'request_path', + 'target_path' => 'target_path', + 'redirect_type' => 'redirect_type', + 'store_id' => '4', + 'description' => 'description', + 'is_autogenerated' => '1', + 'metadata' => '{}' + ] + ]; + + $dataCategoryMapMock = $this->getMock(DataCategoryUrlRewriteDatabaseMap::class, [], [], '', false); + $this->databaseMapPoolMock->expects($this->once()) + ->method('getDataMap') + ->with(DataCategoryUrlRewriteDatabaseMap::class, 1) + ->willReturn($dataCategoryMapMock); + + $this->urlFinderMock->expects($this->never()) + ->method('findAllByData') + ->willReturn([]); + + $dataCategoryMapMock->expects($this->once()) + ->method('getData') + ->willReturn($data); + + $urlRewriteResultArray = $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_CATEGORY, 1); + $this->assertEquals($data[0], $urlRewriteResultArray[0]->toArray()); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php index 28aee3c7c39f463a6740a3cc32cf227207a0d827..b5515c2c254593bd5d7f015584b9b7e62f182bd3 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php @@ -16,31 +16,31 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase { /** @var \Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator */ - protected $currentUrlRewritesRegenerator; - - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $filter; - - /** @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlFinder; + private $currentUrlRewritesRegenerator; /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */ - protected $productUrlPathGenerator; + private $productUrlPathGenerator; /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */ - protected $product; + private $product; /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */ - protected $category; + private $category; /** @var \Magento\CatalogUrlRewrite\Model\ObjectRegistry|\PHPUnit_Framework_MockObject_MockObject */ - protected $objectRegistry; + private $objectRegistry; /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlRewriteFactory; + private $urlRewriteFactory; /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlRewrite; + private $urlRewrite; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $mergeDataProvider; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $urlRewriteFinder; protected function setUp() { @@ -55,28 +55,36 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor()->getMock(); $this->objectRegistry = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Model\ObjectRegistry::class) ->disableOriginalConstructor()->getMock(); - $this->filter = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\Filter::class) - ->disableOriginalConstructor()->getMock(); - $this->filter->expects($this->any())->method('setStoreId')->will($this->returnSelf()); - $this->filter->expects($this->any())->method('setEntityId')->will($this->returnSelf()); - $this->urlFinder = $this->getMockBuilder(\Magento\UrlRewrite\Model\UrlFinderInterface::class) + $this->urlRewriteFinder = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder::class) ->disableOriginalConstructor()->getMock(); + $this->urlRewriteFactory->expects($this->once())->method('create') + ->willReturn($this->urlRewrite); $this->productUrlPathGenerator = $this->getMockBuilder( \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::class )->disableOriginalConstructor()->getMock(); + $mergeDataProviderFactory = $this->getMock( + \Magento\UrlRewrite\Model\MergeDataProviderFactory::class, + ['create'], + [], + '', + false + ); + $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider; + $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider); $this->currentUrlRewritesRegenerator = (new ObjectManager($this))->getObject( \Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator::class, [ - 'urlFinder' => $this->urlFinder, 'productUrlPathGenerator' => $this->productUrlPathGenerator, - 'urlRewriteFactory' => $this->urlRewriteFactory + 'urlRewriteFactory' => $this->urlRewriteFactory, + 'mergeDataProviderFactory' => $mergeDataProviderFactory, + 'urlRewriteFinder' => $this->urlRewriteFinder ] ); } public function testIsAutogeneratedWithoutSaveRewriteHistory() { - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will($this->returnValue($this->getCurrentRewritesMocks([[UrlRewrite::IS_AUTOGENERATED => 1]]))); $this->product->expects($this->once())->method('getData')->with('save_rewrites_history') ->will($this->returnValue(false)); @@ -89,7 +97,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase public function testSkipGenerationForAutogenerated() { - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will($this->returnValue($this->getCurrentRewritesMocks([ [UrlRewrite::IS_AUTOGENERATED => 1, UrlRewrite::REQUEST_PATH => 'same-path'], ]))); @@ -100,7 +108,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $this->assertEquals( [], - $this->currentUrlRewritesRegenerator->generate('store_id', $this->product, $this->objectRegistry) + $this->currentUrlRewritesRegenerator->generate('store_id', $this->product, $this->objectRegistry, 1) ); } @@ -108,21 +116,22 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase { $requestPath = 'autogenerated.html'; $targetPath = 'some-path.html'; + $autoGenerated = 1; $storeId = 2; $productId = 12; $description = 'description'; - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will($this->returnValue($this->getCurrentRewritesMocks([ [ UrlRewrite::REQUEST_PATH => $requestPath, UrlRewrite::TARGET_PATH => 'custom-target-path', UrlRewrite::STORE_ID => $storeId, - UrlRewrite::IS_AUTOGENERATED => 1, + UrlRewrite::IS_AUTOGENERATED => $autoGenerated, UrlRewrite::METADATA => [], UrlRewrite::DESCRIPTION => $description, ], ]))); - $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId)); + $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId)); $this->product->expects($this->once())->method('getData')->with('save_rewrites_history') ->will($this->returnValue(true)); $this->productUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix') @@ -133,6 +142,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $productId, $requestPath, $targetPath, + 0, OptionProvider::PERMANENT, [], $description @@ -140,7 +150,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $this->assertEquals( [$this->urlRewrite], - $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry) + $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry, 1) ); } @@ -149,21 +159,22 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $productId = 12; $requestPath = 'autogenerated.html'; $targetPath = 'simple-product.html'; + $autoGenerated = 1; $storeId = 2; $metadata = ['category_id' => 2, 'some_another_data' => 1]; $description = 'description'; - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will($this->returnValue($this->getCurrentRewritesMocks([ [ UrlRewrite::REQUEST_PATH => $requestPath, UrlRewrite::TARGET_PATH => 'some-path.html', UrlRewrite::STORE_ID => $storeId, - UrlRewrite::IS_AUTOGENERATED => 1, + UrlRewrite::IS_AUTOGENERATED => $autoGenerated, UrlRewrite::METADATA => $metadata, UrlRewrite::DESCRIPTION => $description, ], ]))); - $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId)); + $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId)); $this->product->expects($this->once())->method('getData')->with('save_rewrites_history') ->will($this->returnValue(true)); $this->productUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix') @@ -174,6 +185,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $productId, $requestPath, $targetPath, + 0, OptionProvider::PERMANENT, $metadata, $description @@ -181,13 +193,13 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $this->assertEquals( [$this->urlRewrite], - $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry) + $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry, 2) ); } public function testSkipGenerationForCustom() { - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will($this->returnValue($this->getCurrentRewritesMocks([ [ UrlRewrite::IS_AUTOGENERATED => 0, @@ -210,21 +222,31 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $productId = 123; $requestPath = 'generate-for-custom-without-redirect-type.html'; $targetPath = 'custom-target-path.html'; + $autoGenerated = 0; $description = 'description'; - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will($this->returnValue($this->getCurrentRewritesMocks([ [ UrlRewrite::REQUEST_PATH => $requestPath, UrlRewrite::TARGET_PATH => $targetPath, UrlRewrite::REDIRECT_TYPE => 0, - UrlRewrite::IS_AUTOGENERATED => 0, + UrlRewrite::IS_AUTOGENERATED => $autoGenerated, UrlRewrite::DESCRIPTION => $description, UrlRewrite::METADATA => [], ], ]))); $this->productUrlPathGenerator->expects($this->never())->method('getUrlPathWithSuffix'); - $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId)); - $this->prepareUrlRewriteMock($storeId, $productId, $requestPath, $targetPath, 0, [], $description); + $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId)); + $this->prepareUrlRewriteMock( + $storeId, + $productId, + $requestPath, + $targetPath, + $autoGenerated, + 0, + [], + $description + ); $this->assertEquals( [$this->urlRewrite], @@ -238,22 +260,23 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $productId = 123; $requestPath = 'generate-for-custom-without-redirect-type.html'; $targetPath = 'generated-target-path.html'; + $autoGenerated = 0; $description = 'description'; - $this->urlFinder->expects($this->once())->method('findAllByData') + $this->urlRewriteFinder->expects($this->once())->method('findAllByData') ->will($this->returnValue($this->getCurrentRewritesMocks([ [ UrlRewrite::REQUEST_PATH => $requestPath, UrlRewrite::TARGET_PATH => 'custom-target-path.html', UrlRewrite::REDIRECT_TYPE => 'code', - UrlRewrite::IS_AUTOGENERATED => 0, + UrlRewrite::IS_AUTOGENERATED => $autoGenerated, UrlRewrite::DESCRIPTION => $description, UrlRewrite::METADATA => [], ], ]))); $this->productUrlPathGenerator->expects($this->any())->method('getUrlPathWithSuffix') ->will($this->returnValue($targetPath)); - $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId)); - $this->prepareUrlRewriteMock($storeId, $productId, $requestPath, $targetPath, 'code', [], $description); + $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId)); + $this->prepareUrlRewriteMock($storeId, $productId, $requestPath, $targetPath, 0, 'code', [], $description); $this->assertEquals( [$this->urlRewrite], @@ -287,6 +310,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase * @param mixed $productId * @param mixed $requestPath * @param mixed $targetPath + * @param mixed $autoGenerated * @param mixed $redirectType * @param mixed $metadata * @param mixed $description @@ -296,6 +320,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase $productId, $requestPath, $targetPath, + $autoGenerated, $redirectType, $metadata, $description @@ -310,7 +335,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase ->will($this->returnSelf()); $this->urlRewrite->expects($this->any())->method('setTargetPath')->with($targetPath) ->will($this->returnSelf()); - $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with(0) + $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with($autoGenerated) ->will($this->returnSelf()); $this->urlRewrite->expects($this->any())->method('setRedirectType')->with($redirectType) ->will($this->returnSelf()); diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php index 0e6c501c4981b6dc6e7660ee02824c7c091d4294..7bc0c96cd6e5cbdc159ac4d1ef59edb01bd5ca00 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php @@ -41,8 +41,30 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase /** @var ProductScopeRewriteGenerator */ private $productScopeGenerator; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $mergeDataProvider; + + /** @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject */ + private $serializer; + public function setUp() { + $this->serializer = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class, [], [], '', false); + $this->serializer->expects($this->any()) + ->method('serialize') + ->willReturnCallback( + function ($value) { + return json_encode($value); + } + ); + $this->serializer->expects($this->any()) + ->method('unserialize') + ->willReturnCallback( + function ($value) { + return json_decode($value, true); + } + ); + $this->currentUrlRewritesRegenerator = $this->getMockBuilder( \Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator::class )->disableOriginalConstructor()->getMock(); @@ -61,6 +83,15 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase $this->storeViewService = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Service\V1\StoreViewService::class) ->disableOriginalConstructor()->getMock(); $this->storeManager = $this->getMock(StoreManagerInterface::class); + $mergeDataProviderFactory = $this->getMock( + \Magento\UrlRewrite\Model\MergeDataProviderFactory::class, + ['create'], + [], + '', + false + ); + $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider; + $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider); $this->productScopeGenerator = (new ObjectManager($this))->getObject( \Magento\CatalogUrlRewrite\Model\ProductScopeRewriteGenerator::class, @@ -72,6 +103,7 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase 'objectRegistryFactory' => $this->objectRegistryFactory, 'storeViewService' => $this->storeViewService, 'storeManager' => $this->storeManager, + 'mergeDataProviderFactory' => $mergeDataProviderFactory ] ); } @@ -90,35 +122,35 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase ->method('getParentId') ->willReturn(1); $this->initObjectRegistryFactory([]); - $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite(); - $canonical->setTargetPath('category-1') + $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer); + $canonical->setRequestPath('category-1') ->setStoreId(1); $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate') ->will($this->returnValue([$canonical])); - $categories = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite(); - $categories->setTargetPath('category-2') + $categories = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer); + $categories->setRequestPath('category-2') ->setStoreId(2); $this->categoriesUrlRewriteGenerator->expects($this->any())->method('generate') ->will($this->returnValue([$categories])); - $current = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite(); - $current->setTargetPath('category-3') + $current = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer); + $current->setRequestPath('category-3') ->setStoreId(3); $this->currentUrlRewritesRegenerator->expects($this->any())->method('generate') ->will($this->returnValue([$current])); - $anchorCategories = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite(); - $anchorCategories->setTargetPath('category-4') + $anchorCategories = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer); + $anchorCategories->setRequestPath('category-4') ->setStoreId(4); $this->anchorUrlRewriteGenerator->expects($this->any())->method('generate') ->will($this->returnValue([$anchorCategories])); $this->assertEquals( [ - 'category-1-1' => $canonical, - 'category-2-2' => $categories, - 'category-3-3' => $current, - 'category-4-4' => $anchorCategories + 'category-1_1' => $canonical, + 'category-2_2' => $categories, + 'category-3_3' => $current, + 'category-4_4' => $anchorCategories ], - $this->productScopeGenerator->generateForGlobalScope([$categoryMock], $product) + $this->productScopeGenerator->generateForGlobalScope([$categoryMock], $product, 1) ); } @@ -137,8 +169,8 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase $store->expects($this->any())->method('getRootCategoryId')->will($this->returnValue($storeRootCategoryId)); $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($store)); $this->initObjectRegistryFactory([$category]); - $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite(); - $canonical->setTargetPath('category-1') + $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer); + $canonical->setRequestPath('category-1') ->setStoreId(1); $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate') ->will($this->returnValue([$canonical])); @@ -150,8 +182,8 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue([])); $this->assertEquals( - ['category-1-1' => $canonical], - $this->productScopeGenerator->generateForSpecificStoreView(1, [$category], $product) + ['category-1_1' => $canonical], + $this->productScopeGenerator->generateForSpecificStoreView(1, [$category], $product, 1) ); } @@ -165,7 +197,7 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase $this->storeViewService->expects($this->exactly(2))->method('doesEntityHaveOverriddenUrlKeyForStore') ->will($this->returnValue(true)); - $this->assertEquals([], $this->productScopeGenerator->generateForGlobalScope([], $product)); + $this->assertEquals([], $this->productScopeGenerator->generateForGlobalScope([], $product, 1)); } /** diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php index a392b5231b22bb87d2ad31692870ddb3fe60f7de..81eb66551007d581acaf885a9f684ca0a88451d5 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php @@ -129,6 +129,6 @@ class ProductUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase $this->productScopeRewriteGenerator->expects($this->once()) ->method('generateForSpecificStoreView') ->willReturn($urls); - $this->assertEquals($urls, $this->productUrlRewriteGenerator->generate($productMock)); + $this->assertEquals($urls, $this->productUrlRewriteGenerator->generate($productMock, 1)); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php index eb9977fa1f64fc5c8bbd216f972fc8a886efe66b..069fa1a859a7913f1eb5d1cdd610900fbaff39cb 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php @@ -23,12 +23,12 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase /** * @var string */ - protected $categoryId = 10; + private $categoryId = 10; /** * @var \Magento\UrlRewrite\Model\UrlPersistInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlPersist; + private $urlPersist; /** * @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject @@ -38,12 +38,12 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase /** * @var \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject */ - protected $productUrlRewriteGenerator; + private $productUrlRewriteGenerator; /** * @var \Magento\Catalog\Api\ProductRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $productRepository; + private $productRepository; /** * @var \Magento\CatalogImportExport\Model\Import\Product|\PHPUnit_Framework_MockObject_MockObject @@ -53,67 +53,65 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase /** * @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject */ - protected $observer; + private $observer; /** * @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */ - protected $event; + private $event; /** * @var \Magento\Catalog\Model\ProductFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $catalogProductFactory; + private $catalogProductFactory; /** * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeManager; + private $storeManager; /** * @var \Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $objectRegistryFactory; + private $objectRegistryFactory; /** * @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */ - protected $productUrlPathGenerator; + private $productUrlPathGenerator; /** * @var \Magento\CatalogUrlRewrite\Service\V1\StoreViewService|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeViewService; + private $storeViewService; /** * @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlRewriteFactory; + private $urlRewriteFactory; /** * @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlRewrite; + private $urlRewrite; /** * @var \Magento\CatalogUrlRewrite\Model\ObjectRegistry|\PHPUnit_Framework_MockObject_MockObject */ - protected $objectRegistry; - - /** - * @var \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver|\PHPUnit_Framework_MockObject_MockObject - */ - protected $importMock; + private $objectRegistry; /** * @var \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver */ - protected $import; + private $import; /** * @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */ - protected $product; + private $product; + + /** @var \Magento\UrlRewrite\Model\MergeDataProvider|\PHPUnit_Framework_MockObject_MockObject */ + private $mergeDataProvider; /** * Test products returned by getBunch method of event object. @@ -278,6 +276,15 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase ->expects($this->any()) ->method('getCategoryProcessor') ->willReturn($categoryProcessor); + $mergeDataProviderFactory = $this->getMock( + \Magento\UrlRewrite\Model\MergeDataProviderFactory::class, + ['create'], + [], + '', + false + ); + $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider; + $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider); $this->objectManager = new ObjectManager($this); $this->import = $this->objectManager->getObject( @@ -291,6 +298,7 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase 'urlPersist' => $this->urlPersist, 'urlRewriteFactory' => $this->urlRewriteFactory, 'urlFinder' => $this->urlFinder, + 'mergeDataProviderFactory' => $mergeDataProviderFactory ] ); } @@ -433,14 +441,15 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase $this->urlRewrite->expects($this->any())->method('setRequestPath')->willReturnSelf(); $this->urlRewrite->expects($this->any())->method('setTargetPath')->willReturnSelf(); $this->urlRewrite->expects($this->any())->method('getTargetPath')->willReturn('targetPath'); + $this->urlRewrite->expects($this->any())->method('getRequestPath')->willReturn('requestPath'); $this->urlRewrite->expects($this->any())->method('getStoreId') ->willReturnOnConsecutiveCalls(0, 'not global'); $this->urlRewriteFactory->expects($this->any())->method('create')->willReturn($this->urlRewrite); $productUrls = [ - 'targetPath-0' => $this->urlRewrite, - 'targetPath-not global' => $this->urlRewrite + 'requestPath_0' => $this->urlRewrite, + 'requestPath_not global' => $this->urlRewrite ]; $this->urlPersist diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/UrlRewriteHandlerTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/UrlRewriteHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3c91e485e77e13480c83b81510dfdcdf74611fa2 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/UrlRewriteHandlerTest.php @@ -0,0 +1,123 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Test\Unit\Observer; + +use Magento\CatalogUrlRewrite\Observer\UrlRewriteHandler; +use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider; +use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; +use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\UrlRewrite\Model\UrlPersistInterface; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\UrlRewrite\Model\MergeDataProviderFactory; +use Magento\UrlRewrite\Model\MergeDataProvider; + +class UrlRewriteHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var UrlRewriteHandler + */ + protected $urlRewriteHandler; + + /** + * @var ChildrenCategoriesProvider|\PHPUnit_Framework_MockObject_MockObject + */ + protected $childrenCategoriesProviderMock; + + /** + * @var CategoryUrlRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $categoryUrlRewriteGeneratorMock; + + /** + * @var ProductUrlRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $productUrlRewriteGeneratorMock; + + /** + * @var UrlPersistInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $urlPersistMock; + + /** + * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $collectionFactoryMock; + + /** + * @var MergeDataProviderFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $mergeDataProviderFactoryMock; + + /** + * @var Json|\PHPUnit_Framework_MockObject_MockObject + */ + private $serializerMock; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->childrenCategoriesProviderMock = $this->getMockBuilder(ChildrenCategoriesProvider::class) + ->getMock(); + $this->categoryUrlRewriteGeneratorMock = $this->getMockBuilder(CategoryUrlRewriteGenerator::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productUrlRewriteGeneratorMock = $this->getMockBuilder(ProductUrlRewriteGenerator::class) + ->disableOriginalConstructor() + ->getMock(); + $this->urlPersistMock = $this->getMockBuilder(UrlPersistInterface::class) + ->getMock(); + $this->collectionFactoryMock = $this->getMockBuilder(CollectionFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->mergeDataProviderFactoryMock = $this->getMockBuilder(MergeDataProviderFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $mergeDataProviderMock = $this->getMockBuilder(MergeDataProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->mergeDataProviderFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($mergeDataProviderMock); + + $this->serializerMock = $this->getMockBuilder(Json::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->urlRewriteHandler = new UrlRewriteHandler( + $this->childrenCategoriesProviderMock, + $this->categoryUrlRewriteGeneratorMock, + $this->productUrlRewriteGeneratorMock, + $this->urlPersistMock, + $this->collectionFactoryMock, + $this->mergeDataProviderFactoryMock, + $this->serializerMock + ); + } + + public function testDeleteCategoryRewritesForChildren() + { + $category = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) + ->disableOriginalConstructor() + ->getMock(); + $category->expects($this->once()) + ->method('getId') + ->willReturn(2); + + $this->childrenCategoriesProviderMock->expects($this->once()) + ->method('getChildrenIds') + ->with($category, true) + ->willReturn([3, 4]); + + $this->serializerMock->expects($this->exactly(3)) + ->method('serialize'); + + $this->urlRewriteHandler->deleteCategoryRewritesForChildren($category); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/etc/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/di.xml index 5a8974fc52da4b619b73dc627ea8d35159d5ac71..7f0fc718083ecab1df47dd659bbbcac78e14a77f 100644 --- a/app/code/Magento/CatalogUrlRewrite/etc/di.xml +++ b/app/code/Magento/CatalogUrlRewrite/etc/di.xml @@ -23,4 +23,12 @@ <type name="Magento\UrlRewrite\Model\StorageInterface"> <plugin name="storage_plugin" type="Magento\CatalogUrlRewrite\Model\Category\Plugin\Storage"/> </type> + <type name="Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder"> + <arguments> + <argument name="urlRewriteClassNames" xsi:type="array"> + <item name="product" xsi:type="string">Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap</item> + <item name="category" xsi:type="string">Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/Renderer/ActionsTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/Renderer/ActionsTest.php index 526a1d580a5d5efe0a14109345113edcc22936e9..48c6eb87c4d73755372d66d7172f2a4aa9073bd5 100644 --- a/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/Renderer/ActionsTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/Renderer/ActionsTest.php @@ -65,10 +65,6 @@ class ActionsTest extends \PHPUnit_Framework_TestCase $childNameTwo = 'child.2'; $childNames = [$childNameOne, $childNameTwo]; - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->willReturn(false); - /** * @var Item|\PHPUnit_Framework_MockObject_MockObject $itemMock */ diff --git a/app/code/Magento/Config/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php b/app/code/Magento/Config/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php index cda1a6a80cce639bf511f8400d4f13119a728e5d..e938de4aad64353d54f279525b142612911963bc 100644 --- a/app/code/Magento/Config/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php +++ b/app/code/Magento/Config/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php @@ -9,6 +9,9 @@ */ namespace Magento\Config\Block\System\Config\Form\Fieldset\Modules; +/** + * @deprecated + */ class DisableOutput extends \Magento\Config\Block\System\Config\Form\Fieldset { /** diff --git a/app/code/Magento/Config/Model/Config/Parser/Comment.php b/app/code/Magento/Config/Model/Config/Parser/Comment.php new file mode 100644 index 0000000000000000000000000000000000000000..56dd38d55d800ec27c95b983d53dbb04a144dc87 --- /dev/null +++ b/app/code/Magento/Config/Model/Config/Parser/Comment.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Model\Config\Parser; + +use Magento\Config\Model\Placeholder\Environment; +use Magento\Config\Model\Placeholder\PlaceholderInterface; +use Magento\Framework\App\Config\CommentParserInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Filesystem; + +/** + * Class Comment. It is used to parse config paths from comment section. + */ +class Comment implements CommentParserInterface +{ + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var PlaceholderInterface + */ + private $placeholder; + + /** + * @param Filesystem $filesystem + * @param PlaceholderInterface $placeholder + */ + public function __construct( + Filesystem $filesystem, + PlaceholderInterface $placeholder + ) { + $this->filesystem = $filesystem; + $this->placeholder = $placeholder; + } + + /** + * Retrieves config paths from comment section of the file. + * Example of comment: + * * CONFIG__DEFAULT__SOME__CONF__PATH_ONE + * * CONFIG__DEFAULT__SOME__CONF__PATH_TWO + * This method will return: + * array( + * 'CONFIG__DEFAULT__SOME__CONF__PATH_ONE' => 'some/conf/path_one', + * 'CONFIG__DEFAULT__SOME__CONF__PATH_TWO' => 'some/conf/path_two' + * ); + * + * @param string $fileName + * @return array + * @throws FileSystemException + */ + public function execute($fileName) + { + $fileContent = $this->filesystem + ->getDirectoryRead(DirectoryList::CONFIG) + ->readFile($fileName); + + $pattern = sprintf('/\s+\*\s+(?P<placeholder>%s.*?)\s/', preg_quote(Environment::PREFIX)); + preg_match_all($pattern, $fileContent, $matches); + + if (!isset($matches['placeholder'])) { + return []; + } + + $configs = []; + foreach ($matches['placeholder'] as $placeholder) { + $path = $this->placeholder->restore($placeholder); + $path = preg_replace('/^' . ScopeConfigInterface::SCOPE_TYPE_DEFAULT . '\//', '', $path); + $configs[$placeholder] = $path; + } + + return $configs; + } +} diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Fieldset/Modules/DisableOutputTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Fieldset/Modules/DisableOutputTest.php index 218ea75643939fb116512ced43aca46d7566e5c4..b8f420880ff6d231a36ec2e50126d36eaa149adf 100644 --- a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Fieldset/Modules/DisableOutputTest.php +++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Fieldset/Modules/DisableOutputTest.php @@ -7,6 +7,7 @@ namespace Magento\Config\Test\Unit\Block\System\Config\Form\Fieldset\Modules; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @deprecated */ class DisableOutputTest extends \PHPUnit_Framework_TestCase { diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Parser/CommentTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Parser/CommentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..933f18e1e2919ba442028feada14834313d7b617 --- /dev/null +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Parser/CommentTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Config\Test\Unit\Model\Config\Parser; + +use Magento\Config\Model\Config\Parser\Comment; +use Magento\Config\Model\Placeholder\PlaceholderInterface; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +class CommentTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var PlaceholderInterface|MockObject + */ + private $placeholderMock; + + /** + * @var Filesystem|MockObject + */ + private $fileSystemMock; + + /** + * @var Comment + */ + private $model; + + protected function setUp() + { + $this->placeholderMock = $this->getMockBuilder(PlaceholderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->fileSystemMock = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new Comment( + $this->fileSystemMock, + $this->placeholderMock + ); + } + + public function testExecute() + { + $fileName = 'config.local.php'; + $directoryReadMock = $this->getMockBuilder(ReadInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $directoryReadMock->expects($this->once()) + ->method('readFile') + ->with($fileName) + ->willReturn(file_get_contents(__DIR__ . '/../_files/' . $fileName)); + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->with(DirectoryList::CONFIG) + ->willReturn($directoryReadMock); + $this->placeholderMock->expects($this->any()) + ->method('restore') + ->withConsecutive( + ['CONFIG__DEFAULT__SOME__PAYMENT__PASSWORD'], + ['CONFIG__DEFAULT__SOME__PAYMENT__TOKEN'] + ) + ->willReturnOnConsecutiveCalls( + 'some/payment/password', + 'some/payment/token' + ); + + $this->assertEquals( + $this->model->execute($fileName), + [ + 'CONFIG__DEFAULT__SOME__PAYMENT__PASSWORD' => 'some/payment/password', + 'CONFIG__DEFAULT__SOME__PAYMENT__TOKEN' => 'some/payment/token' + ] + ); + } +} diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/_files/config.local.php b/app/code/Magento/Config/Test/Unit/Model/Config/_files/config.local.php new file mode 100644 index 0000000000000000000000000000000000000000..6c4907567b0320c163e1b0ada2c71259b30a2641 --- /dev/null +++ b/app/code/Magento/Config/Test/Unit/Model/Config/_files/config.local.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +return [ + 'scopes' => [ + 'websites' => [ + 'admin' => [ + 'website_id' => '0' + ], + ], + ], + /** + * The configuration file doesn't contain sensitive data for security reasons. + * Sensitive data can be stored in the following environment variables: + * CONFIG__DEFAULT__SOME__PAYMENT__PASSWORD for some/payment/password + */ + 'system' => [] + /** + * CONFIG__DEFAULT__SOME__PAYMENT__TOKEN for some/payment/token + * test phrase CONFIG__DEFAULT__SOME__PAYMENT__TOKEN for some/payment/token + */ +]; diff --git a/app/code/Magento/Config/etc/di.xml b/app/code/Magento/Config/etc/di.xml index 9579e1ca3191445edd6599eb7b8e779b36c9ab76..ab8973a1a15ceecf227d9de73450b8f906fa0c91 100644 --- a/app/code/Magento/Config/etc/di.xml +++ b/app/code/Magento/Config/etc/di.xml @@ -9,6 +9,7 @@ <preference for="Magento\Config\Model\Config\Structure\SearchInterface" type="Magento\Config\Model\Config\Structure" /> <preference for="Magento\Config\Model\Config\Backend\File\RequestData\RequestDataInterface" type="Magento\Config\Model\Config\Backend\File\RequestData" /> <preference for="Magento\Framework\App\Config\ConfigResource\ConfigInterface" type="Magento\Config\Model\ResourceModel\Config" /> + <preference for="Magento\Framework\App\Config\CommentParserInterface" type="Magento\Config\Model\Config\Parser\Comment" /> <virtualType name="Magento\Framework\View\TemplateEngine\Xhtml\ConfigCompiler" type="Magento\Framework\View\TemplateEngine\Xhtml\Compiler" shared="false"> <arguments> <argument name="compilerText" xsi:type="object">Magento\Framework\View\TemplateEngine\Xhtml\Compiler\Text</argument> @@ -184,4 +185,9 @@ </argument> </arguments> </type> + <type name="Magento\Config\Model\Config\Parser\Comment"> + <arguments> + <argument name="placeholder" xsi:type="object">Magento\Config\Model\Placeholder\Environment</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Customer/etc/config.xml b/app/code/Magento/Customer/etc/config.xml index 540918d44c1394892b441bb57eba07edd67706e7..ccee6914e4ea094dc4a4b664e41ee22ea7457d2c 100644 --- a/app/code/Magento/Customer/etc/config.xml +++ b/app/code/Magento/Customer/etc/config.xml @@ -82,17 +82,15 @@ T: {{var telephone}} {{depend vat_id}}<br/>VAT: {{var vat_id}}{{/depend}}]]></html> <pdf><![CDATA[{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}}| {{depend company}}{{var company}}|{{/depend}} -{{if street1}}{{var street1}} -{{/if}} +{{if street1}}{{var street1}}|{{/if}} {{depend street2}}{{var street2}}|{{/depend}} {{depend street3}}{{var street3}}|{{/depend}} {{depend street4}}{{var street4}}|{{/depend}} -{{if city}}{{var city}},|{{/if}} -{{if region}}{{var region}}, {{/if}}{{if postcode}}{{var postcode}}{{/if}}| +{{if city}}{{var city}}, {{/if}}{{if region}}{{var region}}, {{/if}}{{if postcode}}{{var postcode}}{{/if}}| {{var country}}| -{{depend telephone}}T: {{var telephone}}{{/depend}}| -{{depend fax}}<br/>F: {{var fax}}{{/depend}}| -{{depend vat_id}}<br/>VAT: {{var vat_id}}{{/depend}}|]]></pdf> +{{depend telephone}}T: {{var telephone}}|{{/depend}} +{{depend fax}}F: {{var fax}}|{{/depend}}| +{{depend vat_id}}VAT: {{var vat_id}}{{/depend}}|]]></pdf> </address_templates> </customer> </default> diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorFactory.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..e78b4b71d31184a3f44439d4a168766475b9ad09 --- /dev/null +++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorFactory.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Console\Command\App\SensitiveConfigSet; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\ObjectManagerInterface; + +/** + * Class CollectorFactory creates instance of CollectorInterface. + */ +class CollectorFactory +{ + /**#@+ + * Constant for collector types. + */ + const TYPE_INTERACTIVE = 'interactive'; + const TYPE_SIMPLE = 'simple'; + /**#@-*/ + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var array + */ + private $types; + + /** + * @param ObjectManagerInterface $objectManager + * @param array $types + */ + public function __construct( + ObjectManagerInterface $objectManager, + array $types = [] + ) { + $this->objectManager = $objectManager; + $this->types = $types; + } + + /** + * Create instance of CollectorInterface by given type. + * + * @param string $type + * @return CollectorInterface + * @throws LocalizedException If collector type not exist in registered types array. + */ + public function create($type) + { + if (!isset($this->types[$type])) { + throw new LocalizedException(__('Class for type "%1" was not declared', $type)); + } + + $object = $this->objectManager->create($this->types[$type]); + + if (!$object instanceof CollectorInterface) { + throw new LocalizedException( + __('%1 does not implement %2', get_class($object), CollectorInterface::class) + ); + } + + return $object; + } +} diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorInterface.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..6aecf23adf37d494ba02ae46f2e09bd52fd1079f --- /dev/null +++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/CollectorInterface.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Console\Command\App\SensitiveConfigSet; + +use Magento\Framework\Exception\LocalizedException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Interface CollectorInterface + */ +interface CollectorInterface +{ + /** + * Collects values from user input and return result as array. + * + * @param InputInterface $input + * @param OutputInterface $output + * @param array $configPaths list of available config paths. + * @return array + * @throws LocalizedException + */ + public function getValues(InputInterface $input, OutputInterface $output, array $configPaths); +} diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/InteractiveCollector.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/InteractiveCollector.php new file mode 100644 index 0000000000000000000000000000000000000000..3f67a98ff1c58d49dba645e4987c833d840260d2 --- /dev/null +++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/InteractiveCollector.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Console\Command\App\SensitiveConfigSet; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\QuestionFactory; +use Symfony\Component\Console\Helper\QuestionHelper; + +/** + * Class InteractiveCollector collects configuration values from user input. + */ +class InteractiveCollector implements CollectorInterface +{ + /** + * @var QuestionFactory + */ + private $questionFactory; + + /** + * @var QuestionHelper + */ + private $questionHelper; + + /** + * @param QuestionFactory $questionFactory + * @param QuestionHelper $questionHelper + */ + public function __construct( + QuestionFactory $questionFactory, + QuestionHelper $questionHelper + ) { + $this->questionFactory = $questionFactory; + $this->questionHelper = $questionHelper; + } + + /** + * Collect list of configuration values from user input. + * For example, this method will return + * + * ```php + * [ + * 'some/configuration/path1' => 'someValue1', + * 'some/configuration/path2' => 'someValue2', + * 'some/configuration/path3' => 'someValue3', + * ] + * ``` + * + * {@inheritdoc} + */ + public function getValues(InputInterface $input, OutputInterface $output, array $configPaths) + { + $output->writeln('<info>Please set configuration values or skip them by pressing [Enter]:</info>'); + $values = []; + foreach ($configPaths as $configPath) { + $question = $this->questionFactory->create([ + 'question' => $configPath . ': ' + ]); + $values[$configPath] = $this->questionHelper->ask($input, $output, $question); + } + + return $values; + } +} diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/SimpleCollector.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/SimpleCollector.php new file mode 100644 index 0000000000000000000000000000000000000000..828b4254a53085a986c1b0933912bae3efe7bc0c --- /dev/null +++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSet/SimpleCollector.php @@ -0,0 +1,131 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Console\Command\App\SensitiveConfigSet; + +use Magento\Deploy\Console\Command\App\SensitiveConfigSetCommand; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Question\QuestionFactory; +use Symfony\Component\Console\Helper\QuestionHelper; + +/** + * Class SimpleCollector collects configuration value from user input. + */ +class SimpleCollector implements CollectorInterface +{ + /** + * @var QuestionFactory + */ + private $questionFactory; + + /** + * @var QuestionHelper + */ + private $questionHelper; + + /** + * @param QuestionFactory $questionFactory + * @param QuestionHelper $questionHelper + */ + public function __construct( + QuestionFactory $questionFactory, + QuestionHelper $questionHelper + ) { + $this->questionFactory = $questionFactory; + $this->questionHelper = $questionHelper; + } + + /** + * Collects single configuration value from user input. + * For example, this method will return + * + * ```php + * ['some/configuration/path' => 'someValue'] + * ``` + * + * {@inheritdoc} + */ + public function getValues(InputInterface $input, OutputInterface $output, array $configPaths) + { + $inputPath = $input->getArgument(SensitiveConfigSetCommand::INPUT_ARGUMENT_PATH); + $configPathQuestion = $this->getConfigPathQuestion($configPaths); + $configPath = $inputPath === null ? + $this->questionHelper->ask($input, $output, $configPathQuestion) : + $inputPath; + + $this->validatePath($configPath, $configPaths); + + $inputValue = $input->getArgument(SensitiveConfigSetCommand::INPUT_ARGUMENT_VALUE); + $configValueQuestion = $this->getConfigValueQuestion(); + $configValue = $inputValue === null ? + $this->questionHelper->ask($input, $output, $configValueQuestion) : + $inputValue; + + return [$configPath => $configValue]; + } + + /** + * Get Question to fill configuration path with autocompletion in interactive mode. + * + * @param array $configPaths + * @return Question + */ + private function getConfigPathQuestion(array $configPaths) + { + /** @var Question $configPathQuestion */ + $configPathQuestion = $this->questionFactory->create([ + 'question' => 'Please enter config path: ' + ]); + $configPathQuestion->setAutocompleterValues($configPaths); + $configPathQuestion->setValidator(function ($configPath) use ($configPaths) { + $this->validatePath($configPath, $configPaths); + return $configPath; + }); + + return $configPathQuestion; + } + + /** + * Get Question to fill configuration value in interactive mode. + * + * @return Question + */ + private function getConfigValueQuestion() + { + /** @var Question $configValueQuestion */ + $configValueQuestion = $this->questionFactory->create([ + 'question' => 'Please enter value: ' + ]); + $configValueQuestion->setValidator(function ($interviewer) { + if (empty($interviewer)) { + throw new LocalizedException(new Phrase('Value can\'t be empty')); + } + return $interviewer; + }); + + return $configValueQuestion; + } + + /** + * Check if entered configuration path is valid, throw LocalizedException otherwise. + * + * @param string $configPath Path that should be validated. + * @param array $configPaths List of allowed paths. + * @return void + * @throws LocalizedException If config path not exist in allowed config paths. + */ + private function validatePath($configPath, array $configPaths) + { + if (!in_array($configPath, $configPaths)) { + throw new LocalizedException( + new Phrase('A configuration with this path does not exist or is not sensitive') + ); + } + } +} diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommand.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..e8555ceb9530f59561dce02ed264b3f0de3ee72b --- /dev/null +++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommand.php @@ -0,0 +1,196 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Console\Command\App; + +use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorFactory; +use Magento\Deploy\Model\ConfigWriter; +use Magento\Framework\App\Config\CommentParserInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Scope\ValidatorInterface; +use Magento\Framework\Config\File\ConfigFilePool; +use Magento\Framework\Console\Cli; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Exception\LocalizedException; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Command for set sensitive variable through deploy process. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class SensitiveConfigSetCommand extends Command +{ + /**#@+ + * Names of input arguments or options. + */ + const INPUT_OPTION_INTERACTIVE = 'interactive'; + const INPUT_OPTION_SCOPE = 'scope'; + const INPUT_OPTION_SCOPE_CODE = 'scope-code'; + const INPUT_ARGUMENT_PATH = 'path'; + const INPUT_ARGUMENT_VALUE = 'value'; + /**#@-*/ + + /** + * @var CommentParserInterface + */ + private $commentParser; + + /** + * @var ConfigFilePool + */ + private $configFilePool; + + /** + * @var ConfigWriter + */ + private $configWriter; + + /** + * @var ValidatorInterface + */ + private $scopeValidator; + + /** + * @var CollectorFactory + */ + private $collectorFactory; + + /** + * @param ConfigFilePool $configFilePool + * @param CommentParserInterface $commentParser + * @param ConfigWriter $configWriter + * @param ValidatorInterface $scopeValidator + * @param CollectorFactory $collectorFactory + */ + public function __construct( + ConfigFilePool $configFilePool, + CommentParserInterface $commentParser, + ConfigWriter $configWriter, + ValidatorInterface $scopeValidator, + CollectorFactory $collectorFactory + ) { + parent::__construct(); + $this->commentParser = $commentParser; + $this->configFilePool = $configFilePool; + $this->configWriter = $configWriter; + $this->scopeValidator = $scopeValidator; + $this->collectorFactory = $collectorFactory; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->addArgument( + self::INPUT_ARGUMENT_PATH, + InputArgument::OPTIONAL, + 'Configuration path for example group/section/field_name' + ); + $this->addArgument( + self::INPUT_ARGUMENT_VALUE, + InputArgument::OPTIONAL, + 'Configuration value' + ); + $this->addOption( + self::INPUT_OPTION_INTERACTIVE, + 'i', + InputOption::VALUE_NONE, + 'Enable interactive mode to set all sensitive variables' + ); + $this->addOption( + self::INPUT_OPTION_SCOPE, + null, + InputOption::VALUE_OPTIONAL, + 'Scope for configuration, if not set use \'default\'', + ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ); + $this->addOption( + self::INPUT_OPTION_SCOPE_CODE, + null, + InputOption::VALUE_OPTIONAL, + 'Scope code for configuration, empty string by default', + '' + ); + $this->setName('config:sensitive:set') + ->setDescription('Set sensitive configuration values'); + parent::configure(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $scope = $input->getOption(self::INPUT_OPTION_SCOPE); + $scopeCode = $input->getOption(self::INPUT_OPTION_SCOPE_CODE); + + try { + $this->scopeValidator->isValid($scope, $scopeCode); + $configPaths = $this->getConfigPaths(); + $isInteractive = $input->getOption(self::INPUT_OPTION_INTERACTIVE); + $collector = $this->collectorFactory->create( + $isInteractive ? CollectorFactory::TYPE_INTERACTIVE : CollectorFactory::TYPE_SIMPLE + ); + $values = $collector->getValues($input, $output, $configPaths); + $this->configWriter->save($values, $scope, $scopeCode); + } catch (LocalizedException $e) { + $output->writeln(sprintf('<error>%s</error>', $e->getMessage())); + return Cli::RETURN_FAILURE; + } + + $this->writeSuccessMessage($output, $isInteractive); + + return Cli::RETURN_SUCCESS; + } + + /** + * Writes success message. + * + * @param OutputInterface $output + * @param boolean $isInteractive + * @return void + */ + private function writeSuccessMessage(OutputInterface $output, $isInteractive) + { + $output->writeln(sprintf( + '<info>Configuration value%s saved in app/etc/%s</info>', + $isInteractive ? 's' : '', + $this->configFilePool->getPath(ConfigFilePool::APP_CONFIG) + )); + } + + /** + * Get sensitive configuration paths. + * + * @return array + * @throws LocalizedException if configuration file not exists or sensitive configuration is empty + */ + private function getConfigPaths() + { + $configFilePath = $this->configFilePool->getPathsByPool(ConfigFilePool::LOCAL)[ConfigFilePool::APP_CONFIG]; + try { + $configPaths = $this->commentParser->execute($configFilePath); + } catch (FileSystemException $e) { + throw new LocalizedException(__( + 'File app/etc/%1 can\'t be read. Please check if it exists and has read permissions.', + [ + $configFilePath + ] + )); + } + + if (empty($configPaths)) { + throw new LocalizedException(__('There are no sensitive configurations to fill')); + } + + return $configPaths; + } +} diff --git a/app/code/Magento/Deploy/Model/ConfigWriter.php b/app/code/Magento/Deploy/Model/ConfigWriter.php new file mode 100644 index 0000000000000000000000000000000000000000..7a9b70c528ac9e5d9010e0683b651823ecb74c4a --- /dev/null +++ b/app/code/Magento/Deploy/Model/ConfigWriter.php @@ -0,0 +1,109 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Model; + +use Magento\Config\App\Config\Type\System; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\DeploymentConfig\Writer; +use Magento\Framework\Config\File\ConfigFilePool; +use Magento\Framework\Stdlib\ArrayManager; + +/** + * Class ConfigWriter. Save configuration values into config file. + */ +class ConfigWriter +{ + /** + * @var Writer + */ + private $writer; + + /** + * @var ArrayManager + */ + private $arrayManager; + + /** + * @param Writer $writer + * @param ArrayManager $arrayManager + */ + public function __construct( + Writer $writer, + ArrayManager $arrayManager + ) { + $this->writer = $writer; + $this->arrayManager = $arrayManager; + } + + /** + * Save given list of configuration values into config file. + * + * @param array $values the configuration values (path-value pairs) to be saved. + * @param string $scope scope in which configuration would be saved. + * @param string|null $scopeCode + * @return void + */ + public function save(array $values, $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null) + { + $config = []; + $pathPrefix = $this->getPathPrefix($scope, $scopeCode); + foreach ($values as $configPath => $configValue) { + $fullConfigPath = $pathPrefix . $configPath; + $config = $this->setConfig($config, $fullConfigPath, $configValue); + } + + $this->writer + ->saveConfig( + [ConfigFilePool::APP_CONFIG => $config] + ); + } + + /** + * Apply configuration value into configuration array by given path. + * Ignore values that equal to null. + * + * @param array $config + * @param string $configPath + * @param string $configValue + * @return array + */ + private function setConfig(array $config, $configPath, $configValue) + { + if ($configValue === null) { + return $config; + } + + $config = $this->arrayManager->set( + $configPath, + $config, + $configValue + ); + + return $config; + } + + /** + * Generate config prefix from given $scope and $scopeCode. + * If $scope isn't equal to 'default' and $scopeCode isn't empty put $scopeCode into prefix path, + * otherwise ignore $scopeCode. + * + * @param string $scope + * @param string $scopeCode + * @return string + */ + private function getPathPrefix($scope, $scopeCode) + { + $pathPrefixes = [System::CONFIG_TYPE, $scope]; + if ( + $scope !== ScopeConfigInterface::SCOPE_TYPE_DEFAULT + && !empty($scopeCode) + ) { + $pathPrefixes[] = $scopeCode; + } + + return implode('/', $pathPrefixes) . '/'; + } +} diff --git a/app/code/Magento/Deploy/Model/Filesystem.php b/app/code/Magento/Deploy/Model/Filesystem.php index 83de641c4442a445cceb020cad060ce2c4809f5b..7918207934759bd1cb003e676c3b74a41330777c 100644 --- a/app/code/Magento/Deploy/Model/Filesystem.php +++ b/app/code/Magento/Deploy/Model/Filesystem.php @@ -13,7 +13,8 @@ use Magento\Framework\Exception\LocalizedException; use Magento\User\Model\ResourceModel\User\Collection as UserCollection; /** - * Generate static files, compile; clear var/generation, var/di/, var/view_preprocessed and pub/static directories + * Generate static files, compile + * Сlear generated/code, generated/metadata/, var/view_preprocessed and pub/static directories * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -129,7 +130,7 @@ class Filesystem public function regenerateStatic( OutputInterface $output ) { - // Сlear var/generation, var/di/, var/view_preprocessed and pub/static directories + // Сlear generated/code, generated/metadata/, var/view_preprocessed and pub/static directories $this->cleanupFilesystem( [ DirectoryList::CACHE, @@ -315,7 +316,7 @@ class Filesystem */ public function lockStaticResources() { - // Lock /var/generation, /var/di/ and /var/view_preprocessed directories + // Lock /generated/code, /generated/metadata/ and /var/view_preprocessed directories $this->changePermissions( [ DirectoryList::GENERATION, diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/CollectorFactoryTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/CollectorFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c16fa1d7c176db14d525444a3f18e2231258a31f --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/CollectorFactoryTest.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Test\Unit\Console\Command\App\SensitiveConfigSet; + +use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorFactory; +use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorInterface; +use Magento\Deploy\Console\Command\App\SensitiveConfigSet\InteractiveCollector; +use Magento\Deploy\Console\Command\App\SensitiveConfigSet\SimpleCollector; +use Magento\Framework\ObjectManagerInterface; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use stdClass; + +class CollectorFactoryTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ObjectManagerInterface|MockObject + */ + private $objectManagerMock; + + /** + * @var CollectorFactory + */ + private $model; + + protected function setUp() + { + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->getMockForAbstractClass(); + + $this->model = new CollectorFactory( + $this->objectManagerMock, + [ + CollectorFactory::TYPE_SIMPLE => SimpleCollector::class, + CollectorFactory::TYPE_INTERACTIVE => InteractiveCollector::class, + 'wrongType' => stdClass::class, + ] + ); + } + + public function testCreate() + { + $collectorMock = $this->getMockBuilder(CollectorInterface::class) + ->getMockForAbstractClass(); + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with(SimpleCollector::class) + ->willReturn($collectorMock); + + $this->assertInstanceOf( + CollectorInterface::class, + $this->model->create(CollectorFactory::TYPE_SIMPLE) + ); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Class for type "dummyType" was not declared + */ + public function testCreateNonExisted() + { + $this->model->create('dummyType'); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage stdClass does not implement + */ + public function testCreateWrongImplementation() + { + $type = 'wrongType'; + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with(stdClass::class) + ->willReturn(new stdClass()); + + $this->model->create($type); + } +} diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/InteractiveCollectorTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/InteractiveCollectorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..91576c89463726db1e0f034e72ac96e418770083 --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/InteractiveCollectorTest.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Test\Unit\Console\Command\App\SensitiveConfigSet; + +use Magento\Deploy\Console\Command\App\SensitiveConfigSet\InteractiveCollector; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Question\QuestionFactory; +use Symfony\Component\Console\Helper\QuestionHelper; + +class InteractiveCollectorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var QuestionFactory|MockObject + */ + private $questionFactoryMock; + + /** + * @var QuestionHelper|MockObject + */ + private $questionHelperMock; + + /** + * @var InputInterface|MockObject + */ + private $inputMock; + + /** + * @var OutputInterface|MockObject + */ + private $outputMock; + + /** + * @var InteractiveCollector + */ + private $model; + + protected function setUp() + { + $this->questionFactoryMock = $this->getMockBuilder(QuestionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->questionHelperMock = $this->getMockBuilder(QuestionHelper::class) + ->disableOriginalConstructor() + ->getMock(); + $this->inputMock = $this->getMockBuilder(InputInterface::class) + ->getMockForAbstractClass(); + $this->outputMock = $this->getMockBuilder(OutputInterface::class) + ->getMockForAbstractClass(); + + $this->model = new InteractiveCollector( + $this->questionFactoryMock, + $this->questionHelperMock + ); + } + + public function testGetValues() + { + $configPaths = [ + 'some/config/path1', + 'some/config/path2', + 'some/config/path3' + ]; + + $questionMock = $this->getMockBuilder(Question::class) + ->disableOriginalConstructor() + ->getMock(); + $this->questionHelperMock->expects($this->exactly(3)) + ->method('ask') + ->with($this->inputMock, $this->outputMock, $questionMock) + ->willReturn('someValue'); + $this->questionFactoryMock->expects($this->exactly(3)) + ->method('create') + ->withConsecutive( + [['question' => $configPaths[0] . ': ']], + [['question' => $configPaths[1] . ': ']], + [['question' => $configPaths[2] . ': ']] + ) + ->willReturn($questionMock); + + $this->assertEquals( + [ + 'some/config/path1' => 'someValue', + 'some/config/path2' => 'someValue', + 'some/config/path3' => 'someValue' + ], + $this->model->getValues( + $this->inputMock, + $this->outputMock, + $configPaths + ) + ); + } +} diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/SimpleCollectorTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/SimpleCollectorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..124fc4b0119e377f508e8b1b22439446bd9d8fe4 --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSet/SimpleCollectorTest.php @@ -0,0 +1,187 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Test\Unit\Console\Command\App\SensitiveConfigSet; + +use Magento\Deploy\Console\Command\App\SensitiveConfigSet\SimpleCollector; +use Magento\Deploy\Console\Command\App\SensitiveConfigSetCommand; +use Magento\Framework\Exception\LocalizedException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Question\QuestionFactory; +use Symfony\Component\Console\Helper\QuestionHelper; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +class SimpleCollectorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var QuestionFactory|MockObject + */ + private $questionFactoryMock; + + /** + * @var QuestionHelper|MockObject + */ + private $questionHelperMock; + + /** + * @var InputInterface|MockObject + */ + private $inputMock; + + /** + * @var OutputInterface|MockObject + */ + private $outputMock; + + /** + * @var SimpleCollector + */ + private $model; + + protected function setUp() + { + $this->questionFactoryMock = $this->getMockBuilder(QuestionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->questionHelperMock = $this->getMockBuilder(QuestionHelper::class) + ->disableOriginalConstructor() + ->getMock(); + $this->inputMock = $this->getMockBuilder(InputInterface::class) + ->getMockForAbstractClass(); + $this->outputMock = $this->getMockBuilder(OutputInterface::class) + ->getMockForAbstractClass(); + + $this->model = new SimpleCollector( + $this->questionFactoryMock, + $this->questionHelperMock + ); + } + + public function testGetValues() + { + $configPaths = [ + 'some/config/path1', + 'some/config/path2' + ]; + + $pathQuestionMock = $this->getMockBuilder(Question::class) + ->disableOriginalConstructor() + ->getMock(); + $valueQuestionMock = $this->getMockBuilder(Question::class) + ->disableOriginalConstructor() + ->getMock(); + $this->inputMock->expects($this->exactly(2)) + ->method('getArgument') + ->withConsecutive( + [SensitiveConfigSetCommand::INPUT_ARGUMENT_PATH], + [SensitiveConfigSetCommand::INPUT_ARGUMENT_VALUE] + ) + ->willReturnOnConsecutiveCalls( + $configPaths[0], + 'someValue' + ); + $this->questionFactoryMock->expects($this->exactly(2)) + ->method('create') + ->withConsecutive( + [['question' => 'Please enter config path: ']], + [['question' => 'Please enter value: ']] + ) + ->willReturnOnConsecutiveCalls( + $pathQuestionMock, + $valueQuestionMock + ); + + $this->assertEquals( + ['some/config/path1' => 'someValue'], + $this->model->getValues( + $this->inputMock, + $this->outputMock, + $configPaths + ) + ); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage A configuration with this path does not exist or is not sensitive + */ + public function testWrongConfigPath() + { + $configPaths = [ + 'some/config/path1', + 'some/config/path2' + ]; + + $pathQuestionMock = $this->getMockBuilder(Question::class) + ->disableOriginalConstructor() + ->getMock(); + $this->inputMock->expects($this->once()) + ->method('getArgument') + ->with(SensitiveConfigSetCommand::INPUT_ARGUMENT_PATH) + ->willReturn('some/not_exist/config'); + $this->questionFactoryMock->expects($this->once()) + ->method('create') + ->with(['question' => 'Please enter config path: ']) + ->willReturn($pathQuestionMock); + + $this->model->getValues( + $this->inputMock, + $this->outputMock, + $configPaths + ); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testEmptyValue() + { + $configPaths = [ + 'some/config/path1', + 'some/config/path2' + ]; + $message = 'exception message'; + + $pathQuestionMock = $this->getMockBuilder(Question::class) + ->disableOriginalConstructor() + ->getMock(); + $valueQuestionMock = $this->getMockBuilder(Question::class) + ->disableOriginalConstructor() + ->getMock(); + $this->questionHelperMock->expects($this->once()) + ->method('ask') + ->with($this->inputMock, $this->outputMock, $valueQuestionMock) + ->willThrowException(new LocalizedException(__($message))); + $this->inputMock->expects($this->exactly(2)) + ->method('getArgument') + ->withConsecutive( + [SensitiveConfigSetCommand::INPUT_ARGUMENT_PATH], + [SensitiveConfigSetCommand::INPUT_ARGUMENT_VALUE] + ) + ->willReturnOnConsecutiveCalls( + $configPaths[0], + null + ); + $this->questionFactoryMock->expects($this->exactly(2)) + ->method('create') + ->withConsecutive( + [['question' => 'Please enter config path: ']], + [['question' => 'Please enter value: ']] + ) + ->willReturnOnConsecutiveCalls( + $pathQuestionMock, + $valueQuestionMock + ); + + $this->model->getValues( + $this->inputMock, + $this->outputMock, + $configPaths + ); + } +} diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSetCommandTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSetCommandTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b305ed8249fbff8d80854385f1f3fa79d1204541 --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSetCommandTest.php @@ -0,0 +1,262 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Test\Unit\Console\Command\App; + +use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorFactory; +use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorInterface; +use Magento\Deploy\Console\Command\App\SensitiveConfigSetCommand; +use Magento\Deploy\Model\ConfigWriter; +use Magento\Framework\App\Config\CommentParserInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Scope\ValidatorInterface; +use Magento\Framework\Config\File\ConfigFilePool; +use Magento\Framework\Console\Cli; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Symfony\Component\Console\Tester\CommandTester; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class SensitiveConfigSetCommandTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ConfigFilePool|MockObject + */ + private $configFilePoolMock; + + /** + * @var CommentParserInterface|MockObject + */ + private $commentParserMock; + + /** + * @var ConfigWriter|MockObject + */ + private $configWriterMock; + + /** + * @var ValidatorInterface|MockObject + */ + private $scopeValidatorMock; + + /** + * @var CollectorFactory|MockObject + */ + private $collectorFactoryMock; + + /** + * @var SensitiveConfigSetCommand + */ + private $command; + + public function setUp() + { + $this->configFilePoolMock = $this->getMockBuilder(ConfigFilePool::class) + ->disableOriginalConstructor() + ->getMock(); + $this->commentParserMock = $this->getMockBuilder(CommentParserInterface::class) + ->getMockForAbstractClass(); + $this->configWriterMock = $this->getMockBuilder(ConfigWriter::class) + ->disableOriginalConstructor() + ->getMock(); + $this->scopeValidatorMock = $this->getMockBuilder(ValidatorInterface::class) + ->getMockForAbstractClass(); + $this->collectorFactoryMock = $this->getMockBuilder(CollectorFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->command = new SensitiveConfigSetCommand( + $this->configFilePoolMock, + $this->commentParserMock, + $this->configWriterMock, + $this->scopeValidatorMock, + $this->collectorFactoryMock + ); + } + + public function testConfigFileNotExist() + { + $this->configFilePoolMock->expects($this->once()) + ->method('getPathsByPool') + ->with(ConfigFilePool::LOCAL) + ->willReturn([ + ConfigFilePool::APP_CONFIG => 'config.local.php' + ]); + $this->scopeValidatorMock->expects($this->once()) + ->method('isValid') + ->with('default', '') + ->willReturn(true); + $this->commentParserMock->expects($this->any()) + ->method('execute') + ->willThrowException(new FileSystemException(new Phrase('some message'))); + + $tester = new CommandTester($this->command); + $tester->execute([ + 'path' => 'some/path', + 'value' => 'some value' + ]); + + $this->assertEquals( + Cli::RETURN_FAILURE, + $tester->getStatusCode() + ); + $this->assertContains( + 'File app/etc/config.local.php can\'t be read. ' + . 'Please check if it exists and has read permissions.', + $tester->getDisplay() + ); + } + + public function testWriterException() + { + $exceptionMessage = 'exception'; + $this->scopeValidatorMock->expects($this->once()) + ->method('isValid') + ->with('default', '') + ->willReturn(true); + $this->commentParserMock->expects($this->once()) + ->method('execute') + ->willReturn([ + 'some/config/path1', + 'some/config/path2' + ]); + $collectorMock = $this->getMockBuilder(CollectorInterface::class) + ->getMockForAbstractClass(); + $collectorMock->expects($this->once()) + ->method('getValues') + ->willReturn(['some/config/pathNotExist' => 'value']); + $this->collectorFactoryMock->expects($this->once()) + ->method('create') + ->with(CollectorFactory::TYPE_SIMPLE) + ->willReturn($collectorMock); + $this->configWriterMock->expects($this->once()) + ->method('save') + ->willThrowException(new LocalizedException(__($exceptionMessage))); + + $tester = new CommandTester($this->command); + $tester->execute([ + 'path' => 'some/config/pathNotExist', + 'value' => 'some value' + ]); + + $this->assertEquals( + Cli::RETURN_FAILURE, + $tester->getStatusCode() + ); + $this->assertContains( + $exceptionMessage, + $tester->getDisplay() + ); + } + + public function testEmptyConfigPaths() + { + $this->scopeValidatorMock->expects($this->once()) + ->method('isValid') + ->with('default', '') + ->willReturn(true); + $this->commentParserMock->expects($this->once()) + ->method('execute') + ->willReturn([]); + + $tester = new CommandTester($this->command); + $tester->execute([ + 'path' => 'some/config/pathNotExist', + 'value' => 'some value' + ]); + + $this->assertEquals( + Cli::RETURN_FAILURE, + $tester->getStatusCode() + ); + $this->assertContains( + 'There are no sensitive configurations to fill', + $tester->getDisplay() + ); + } + + public function testExecute() + { + $collectedValues = ['some/config/path1' => 'value']; + $this->scopeValidatorMock->expects($this->once()) + ->method('isValid') + ->with('default', '') + ->willReturn(true); + $this->commentParserMock->expects($this->once()) + ->method('execute') + ->willReturn([ + 'some/config/path1', + 'some/config/path2' + ]); + $collectorMock = $this->getMockBuilder(CollectorInterface::class) + ->getMockForAbstractClass(); + $collectorMock->expects($this->once()) + ->method('getValues') + ->willReturn($collectedValues); + $this->collectorFactoryMock->expects($this->once()) + ->method('create') + ->with(CollectorFactory::TYPE_SIMPLE) + ->willReturn($collectorMock); + $this->configWriterMock->expects($this->once()) + ->method('save') + ->with($collectedValues, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, ''); + + $tester = new CommandTester($this->command); + $tester->execute([]); + + $this->assertEquals( + Cli::RETURN_SUCCESS, + $tester->getStatusCode() + ); + $this->assertContains( + 'Configuration value saved in', + $tester->getDisplay() + ); + } + + public function testExecuteInteractive() + { + $collectedValues = ['some/config/path1' => 'value']; + $this->scopeValidatorMock->expects($this->once()) + ->method('isValid') + ->with('default', '') + ->willReturn(true); + $this->commentParserMock->expects($this->once()) + ->method('execute') + ->willReturn([ + 'some/config/path1', + 'some/config/path2', + 'some/config/path3' + ]); + $collectorMock = $this->getMockBuilder(CollectorInterface::class) + ->getMockForAbstractClass(); + $collectorMock->expects($this->once()) + ->method('getValues') + ->willReturn($collectedValues); + $this->collectorFactoryMock->expects($this->once()) + ->method('create') + ->with(CollectorFactory::TYPE_INTERACTIVE) + ->willReturn($collectorMock); + $this->configWriterMock->expects($this->once()) + ->method('save') + ->with($collectedValues, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, ''); + + $tester = new CommandTester($this->command); + $tester->execute(['--interactive' => true]); + + $this->assertEquals( + Cli::RETURN_SUCCESS, + $tester->getStatusCode() + ); + $this->assertContains( + 'Configuration values saved in', + $tester->getDisplay() + ); + } +} diff --git a/app/code/Magento/Deploy/Test/Unit/Model/ConfigWriterTest.php b/app/code/Magento/Deploy/Test/Unit/Model/ConfigWriterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9ac0097d5f73b69ea9bf02cafa7cd4773d9a810e --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Model/ConfigWriterTest.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Test\Unit\Model; + +use Magento\Deploy\Model\ConfigWriter; +use Magento\Framework\App\DeploymentConfig\Writer; +use Magento\Framework\Config\File\ConfigFilePool; +use Magento\Framework\Stdlib\ArrayManager; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +class ConfigWriterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Writer|MockObject + */ + private $writerMock; + + /** + * @var ArrayManager|MockObject + */ + private $arrayManagerMock; + + /** + * @var ConfigWriter + */ + private $model; + + public function setUp() + { + $this->arrayManagerMock = $this->getMockBuilder(ArrayManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->writerMock = $this->getMockBuilder(Writer::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new ConfigWriter( + $this->writerMock, + $this->arrayManagerMock + ); + } + + public function testSave() + { + $values = [ + 'some1/config1/path1' => 'someValue1', + 'some2/config2/path2' => 'someValue2', + 'some3/config3/path3' => 'someValue3' + ]; + $config = ['system' => []]; + + $this->arrayManagerMock->expects($this->exactly(3)) + ->method('set') + ->withConsecutive( + ['system/scope/scope_code/some1/config1/path1', $this->anything(), 'someValue1'], + ['system/scope/scope_code/some2/config2/path2', $this->anything(), 'someValue2'], + ['system/scope/scope_code/some3/config3/path3', $this->anything(), 'someValue3'] + ) + ->willReturn($config); + $this->writerMock->expects($this->once()) + ->method('saveConfig') + ->with([ConfigFilePool::APP_CONFIG => $config]); + + $this->model->save($values, 'scope', 'scope_code'); + } + + public function testSaveDefaultScope() + { + $values = [ + 'some1/config1/path1' => 'someValue1', + 'some2/config2/path2' => 'someValue2', + 'some3/config3/path3' => 'someValue3' + ]; + $config = ['system' => []]; + + $this->arrayManagerMock->expects($this->exactly(3)) + ->method('set') + ->withConsecutive( + ['system/default/some1/config1/path1', $this->anything(), 'someValue1'], + ['system/default/some2/config2/path2', $this->anything(), 'someValue2'], + ['system/default/some3/config3/path3', $this->anything(), 'someValue3'] + ) + ->willReturn($config); + $this->writerMock->expects($this->once()) + ->method('saveConfig') + ->with([ConfigFilePool::APP_CONFIG => $config]); + + $this->model->save($values); + } +} diff --git a/app/code/Magento/Deploy/composer.json b/app/code/Magento/Deploy/composer.json index ed0b8520ca645afa8af2506616f9dccde6965f08..436c3a6451c39b3e65ae65e3740b402bbc75fe80 100644 --- a/app/code/Magento/Deploy/composer.json +++ b/app/code/Magento/Deploy/composer.json @@ -6,7 +6,8 @@ "magento/framework": "100.2.*", "magento/module-store": "100.2.*", "magento/module-require-js": "100.2.*", - "magento/module-user": "100.2.*" + "magento/module-user": "100.2.*", + "magento/module-config": "100.2.*" }, "type": "magento2-module", "version": "100.2.0-dev", diff --git a/app/code/Magento/Deploy/etc/di.xml b/app/code/Magento/Deploy/etc/di.xml index 68fa7d909e4df8a3b13f3ef01a8f178d804feb4d..6751dd1e0b161dfee948436818a7f38c50025daf 100644 --- a/app/code/Magento/Deploy/etc/di.xml +++ b/app/code/Magento/Deploy/etc/di.xml @@ -26,6 +26,7 @@ <item name="setModeCommand" xsi:type="object">Magento\Deploy\Console\Command\SetModeCommand</item> <item name="showModeCommand" xsi:type="object">Magento\Deploy\Console\Command\ShowModeCommand</item> <item name="dumpApplicationCommand" xsi:type="object">\Magento\Deploy\Console\Command\App\ApplicationDumpCommand</item> + <item name="sensitiveConfigSetCommand" xsi:type="object">\Magento\Deploy\Console\Command\App\SensitiveConfigSetCommand</item> </argument> </arguments> </type> @@ -34,4 +35,12 @@ <argument name="shell" xsi:type="object">Magento\Framework\App\Shell</argument> </arguments> </type> + <type name="Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorFactory"> + <arguments> + <argument name="types" xsi:type="array"> + <item name="interactive" xsi:type="string">Magento\Deploy\Console\Command\App\SensitiveConfigSet\InteractiveCollector</item> + <item name="simple" xsi:type="string">Magento\Deploy\Console\Command\App\SensitiveConfigSet\SimpleCollector</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Fedex/etc/di.xml b/app/code/Magento/Fedex/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..e2f5142cde96b5f82c10175da290e680e79f419c --- /dev/null +++ b/app/code/Magento/Fedex/etc/di.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2013-2017 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\Config\Model\Config\Export\ExcludeList"> + <arguments> + <argument name="configs" xsi:type="array"> + <item name="carriers/fedex/account" xsi:type="string">1</item> + <item name="carriers/fedex/key" xsi:type="string">1</item> + <item name="carriers/fedex/meter_number" xsi:type="string">1</item> + <item name="carriers/fedex/password" xsi:type="string">1</item> + </argument> + </arguments> + </type> +</config> \ No newline at end of file diff --git a/app/code/Magento/Payment/Test/Unit/Block/Info/SubstitutionTest.php b/app/code/Magento/Payment/Test/Unit/Block/Info/SubstitutionTest.php index 78946d236391e90eccec0d29def00f810db91eea..f4a2b1a81d7feeead8c6365f5291b6dd9466f02a 100644 --- a/app/code/Magento/Payment/Test/Unit/Block/Info/SubstitutionTest.php +++ b/app/code/Magento/Payment/Test/Unit/Block/Info/SubstitutionTest.php @@ -3,9 +3,6 @@ * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - namespace Magento\Payment\Test\Unit\Block\Info; /** @@ -31,71 +28,23 @@ class SubstitutionTest extends \PHPUnit_Framework_TestCase protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->layout = $this->getMockBuilder( - \Magento\Framework\View\LayoutInterface::class - )->disableOriginalConstructor()->setMethods( - [] - )->getMock(); - - $eventManager = $this->getMockBuilder( - \Magento\Framework\Event\ManagerInterface::class - )->disableOriginalConstructor()->setMethods( - [] - )->getMock(); - - $scopeConfig = $this->getMockBuilder( - \Magento\Framework\App\Config\ScopeConfigInterface::class - )->disableOriginalConstructor()->setMethods( - [] - )->getMock(); - $scopeConfig->expects( - $this->any() - )->method( - 'getValue' - )->with( - $this->stringContains( - 'advanced/modules_disable_output/' - ), - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - )->will( - $this->returnValue( - false - ) - ); - - $context = $this->getMockBuilder( - \Magento\Framework\View\Element\Template\Context::class - )->disableOriginalConstructor()->setMethods( - ['getLayout', 'getEventManager', 'getScopeConfig'] - )->getMock(); - $context->expects( - $this->any() - )->method( - 'getLayout' - )->will( - $this->returnValue( - $this->layout - ) - ); - $context->expects( - $this->any() - )->method( - 'getEventManager' - )->will( - $this->returnValue( - $eventManager - ) - ); - $context->expects( - $this->any() - )->method( - 'getScopeConfig' - )->will( - $this->returnValue( - $scopeConfig - ) - ); + $this->layout = $this->getMockBuilder(\Magento\Framework\View\LayoutInterface::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $eventManager = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $context = $this->getMockBuilder(\Magento\Framework\View\Element\Template\Context::class) + ->disableOriginalConstructor() + ->setMethods(['getLayout', 'getEventManager', 'getScopeConfig']) + ->getMock(); + $context->expects($this->any()) + ->method('getLayout') + ->willReturn($this->layout); + $context->expects($this->any())->method('getEventManager') + ->willReturn($eventManager); $this->block = $this->objectManager->getObject( \Magento\Payment\Block\Info\Substitution::class, @@ -110,52 +59,45 @@ class SubstitutionTest extends \PHPUnit_Framework_TestCase public function testBeforeToHtml() { - $abstractBlock = $this->getMockBuilder( - \Magento\Framework\View\Element\AbstractBlock::class - )->disableOriginalConstructor()->setMethods( - [] - )->getMock(); - $childAbstractBlock = clone($abstractBlock); - - $abstractBlock->expects($this->any())->method('getParentBlock')->will($this->returnValue($childAbstractBlock)); + $abstractBlock = $this->getMockBuilder(\Magento\Framework\View\Element\AbstractBlock::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $childAbstractBlock = clone $abstractBlock; + + $abstractBlock->expects($this->any()) + ->method('getParentBlock') + ->willReturn($childAbstractBlock); + $this->layout->expects($this->any()) + ->method('getParentName') + ->willReturn('parentName'); + $this->layout->expects($this->any()) + ->method('getBlock') + ->willReturn($abstractBlock); + + $infoMock = $this->getMockBuilder(\Magento\Payment\Model\Info::class) + ->disableOriginalConstructor() + ->getMock(); + $methodMock = $this->getMockBuilder(\Magento\Payment\Model\MethodInterface::class) + ->getMockForAbstractClass(); + $infoMock->expects($this->once()) + ->method('getMethodInstance') + ->willReturn($methodMock); - $this->layout->expects($this->any())->method('getParentName')->will($this->returnValue('parentName')); - $this->layout->expects($this->any())->method('getBlock')->will($this->returnValue($abstractBlock)); - - $infoMock = $this->getMockBuilder( - \Magento\Payment\Model\Info::class - )->disableOriginalConstructor()->setMethods( - [] - )->getMock(); - $methodMock = $this->getMockBuilder( - \Magento\Payment\Model\MethodInterface::class - )->getMockForAbstractClass(); - $infoMock->expects($this->once())->method('getMethodInstance')->will($this->returnValue($methodMock)); $this->block->setInfo($infoMock); $fakeBlock = new \StdClass(); - $this->layout->expects( - $this->any() - )->method( - 'createBlock' - )->with( - \Magento\Framework\View\Element\Template::class, - '', - ['data' => ['method' => $methodMock, 'template' => 'Magento_Payment::info/substitution.phtml']] - )->will( - $this->returnValue( - $fakeBlock - ) - ); - - $childAbstractBlock->expects( - $this->any() - )->method( - 'setChild' - )->with( - 'order_payment_additional', - $fakeBlock - ); + $this->layout->expects($this->any()) + ->method('createBlock') + ->with( + \Magento\Framework\View\Element\Template::class, + '', + ['data' => ['method' => $methodMock, 'template' => 'Magento_Payment::info/substitution.phtml']] + )->willReturn($fakeBlock); + + $childAbstractBlock->expects($this->any()) + ->method('setChild') + ->with('order_payment_additional', $fakeBlock); $this->block->toHtml(); } diff --git a/app/code/Magento/Paypal/Test/Unit/Block/Billing/AgreementsTest.php b/app/code/Magento/Paypal/Test/Unit/Block/Billing/AgreementsTest.php index f478ea085e47a378573fc945a1afdf77b6eb942a..64f278c8e9f118b6b7e564da0c6de434052d4184 100644 --- a/app/code/Magento/Paypal/Test/Unit/Block/Billing/AgreementsTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Block/Billing/AgreementsTest.php @@ -243,10 +243,6 @@ class AgreementsTest extends \PHPUnit_Framework_TestCase ->expects($this->at(1)) ->method('dispatch') ->with('view_block_abstract_to_html_after', ['block' => $this->block, 'transport' => $transport]); - $this->scopeConfig - ->expects($this->once()) - ->method('getValue') - ->willReturn(false); $this->urlBuilder->expects($this->once())->method('getUrl')->with('paypal/billing_agreement/startWizard', []); $this->block->toHtml(); } diff --git a/app/code/Magento/Paypal/Test/Unit/Block/Express/ReviewTest.php b/app/code/Magento/Paypal/Test/Unit/Block/Express/ReviewTest.php index cf16349e5873468cef013f42601895a28244cba5..17468c02578693be75efd4af8bbef3f73d868871 100644 --- a/app/code/Magento/Paypal/Test/Unit/Block/Express/ReviewTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Block/Express/ReviewTest.php @@ -3,11 +3,9 @@ * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Paypal\Test\Unit\Block\Express; use Magento\Paypal\Block\Express\Review; -use Magento\Quote\Model\Quote\Address\Rate; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -35,14 +33,6 @@ class ReviewTest extends \PHPUnit_Framework_TestCase $layout = $this->getMock(\Magento\Framework\View\LayoutInterface::class, [], [], '', false); $eventManager = $this->getMock(\Magento\Framework\Event\ManagerInterface::class, [], [], '', false); - $scopeConfig = $this->getMock(\Magento\Framework\App\Config\ScopeConfigInterface::class, [], [], '', false); - - $scopeConfig->expects($this->any()) - ->method('getValue') - ->with( - $this->stringContains('advanced/modules_disable_output/'), - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - )->will($this->returnValue(false)); $urlBuilder = $this->getMock(\Magento\Framework\UrlInterface::class); $urlBuilder->expects($this->any())->method('getUrl')->will($this->returnArgument(0)); @@ -60,7 +50,6 @@ class ReviewTest extends \PHPUnit_Framework_TestCase $context->expects($this->any())->method('getLayout')->will($this->returnValue($layout)); $context->expects($this->any())->method('getEventManager')->will($this->returnValue($eventManager)); - $context->expects($this->any())->method('getScopeConfig')->will($this->returnValue($scopeConfig)); $context->expects($this->any())->method('getRequest')->will($this->returnValue($this->request)); $context->expects($this->any())->method('getAssetRepository')->will($this->returnValue($this->assetRepo)); $context->expects($this->any())->method('getUrlBuilder')->will($this->returnValue($urlBuilder)); diff --git a/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php b/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php index 44b494f2a41df8c7394c83396387d577bbb5e641..f80c1018d322c35bd8dd823e2c6e1d0d58fca96b 100644 --- a/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php @@ -149,8 +149,7 @@ class AdditionalTest extends \PHPUnit_Framework_TestCase '', false, true, - true, - ['getValue'] + true ); $this->cacheStateMock = $this->getMockForAbstractClass( \Magento\Framework\App\Cache\StateInterface::class, @@ -267,12 +266,6 @@ class AdditionalTest extends \PHPUnit_Framework_TestCase $this->eventManagerMock->expects($this->at(1)) ->method('dispatch') ->with('view_block_abstract_to_html_after'); - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with( - 'advanced/modules_disable_output/Magento_Persistent', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - )->willReturn(false); // get cache $this->cacheStateMock->expects($this->at(0)) diff --git a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php index 6f63d94fd00ba7bc21de39313e3aa3809fd075d4..a3186aef6247d4729de611a73910d566f71e0326 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php @@ -395,7 +395,10 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject if ($putOrderId) { $page->drawText(__('Order # ') . $order->getRealOrderId(), 35, $top -= 30, 'UTF-8'); + $top +=15; } + + $top -=30; $page->drawText( __('Order Date: ') . $this->_localeDate->formatDate( @@ -408,7 +411,7 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject false ), 35, - $top -= 15, + $top, 'UTF-8' ); diff --git a/app/code/Magento/SampleData/etc/module.xml b/app/code/Magento/SampleData/etc/module.xml index 24be4417e689facd4548df8c6e6e6804bc8f7700..e511dfd4bbb765c5e0031bea552dbdc354bb8dc3 100644 --- a/app/code/Magento/SampleData/etc/module.xml +++ b/app/code/Magento/SampleData/etc/module.xml @@ -7,5 +7,8 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Magento_SampleData" setup_version="2.0.0"> + <sequence> + <module name="Magento_UrlRewrite"/> + </sequence> </module> </config> diff --git a/app/code/Magento/Store/Model/Scope/Validator.php b/app/code/Magento/Store/Model/Scope/Validator.php new file mode 100644 index 0000000000000000000000000000000000000000..f2222fdc476a9a61ee9ed37c285332561f6dd43b --- /dev/null +++ b/app/code/Magento/Store/Model/Scope/Validator.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Store\Model\Scope; + +use InvalidArgumentException; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Scope\ValidatorInterface; +use Magento\Framework\App\ScopeResolverPool; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; + +/** + * Class Validator validates scope and scope code. + */ +class Validator implements ValidatorInterface +{ + /** + * @var ScopeResolverPool + */ + private $scopeResolverPool; + + /** + * @param ScopeResolverPool $scopeResolverPool + */ + public function __construct(ScopeResolverPool $scopeResolverPool) + { + $this->scopeResolverPool = $scopeResolverPool; + } + + /** + * {@inheritdoc} + */ + public function isValid($scope, $scopeCode = null) + { + if ($scope === ScopeConfigInterface::SCOPE_TYPE_DEFAULT && empty($scopeCode)) { + return true; + } + + if ($scope === ScopeConfigInterface::SCOPE_TYPE_DEFAULT && !empty($scopeCode)) { + throw new LocalizedException(__( + 'The "%1" scope can\'t include a scope code. Try again without entering a scope code.', + ScopeConfigInterface::SCOPE_TYPE_DEFAULT + )); + } + + if (empty($scope)) { + throw new LocalizedException(__('Enter a scope before proceeding.')); + } + + $this->validateScopeCode($scopeCode); + + try { + $scopeResolver = $this->scopeResolverPool->get($scope); + $scopeResolver->getScope($scopeCode)->getId(); + } catch (InvalidArgumentException $e) { + throw new LocalizedException(__('The "%1" value doesn\'t exist. Enter another value.', $scope)); + } catch (NoSuchEntityException $e) { + throw new LocalizedException(__('The "%1" value doesn\'t exist. Enter another value.', $scopeCode)); + } + + return true; + } + + /** + * Validate scope code + * Throw exception if not valid. + * + * @param string $scopeCode + * @return void + * @throws LocalizedException if scope code is empty or has a wrong format + */ + private function validateScopeCode($scopeCode) + { + if (empty($scopeCode)) { + throw new LocalizedException(__('Enter a scope code before proceeding.')); + } + + if (!preg_match('/^[a-z]+[a-z0-9_]*$/', $scopeCode)) { + throw new LocalizedException(__( + 'The scope code can include only lowercase letters (a-z), numbers (0-9) and underscores (_). ' + . 'Also, the first character must be a letter.' + )); + } + } +} diff --git a/app/code/Magento/Store/Test/Unit/Model/Scope/ValidatorTest.php b/app/code/Magento/Store/Test/Unit/Model/Scope/ValidatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..51c58739f5ff5c10f19ebcbef9d7a92df33611b0 --- /dev/null +++ b/app/code/Magento/Store/Test/Unit/Model/Scope/ValidatorTest.php @@ -0,0 +1,138 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Store\Test\Unit\Model\Scope; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ScopeInterface; +use Magento\Framework\App\ScopeResolverInterface; +use Magento\Framework\App\ScopeResolverPool; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\Scope\Validator; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +class ValidatorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Validator + */ + private $model; + + /** + * @var ScopeResolverPool|MockObject + */ + private $scopeResolverPoolMock; + + protected function setUp() + { + $this->scopeResolverPoolMock = $this->getMockBuilder(ScopeResolverPool::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new Validator( + $this->scopeResolverPoolMock + ); + } + + public function testIsValid() + { + $scope = 'not_default_scope'; + $scopeCode = 'not_exist_scope_code'; + + $scopeResolver = $this->getMockBuilder(ScopeResolverInterface::class) + ->getMockForAbstractClass(); + $scopeObject = $this->getMockBuilder(ScopeInterface::class) + ->getMockForAbstractClass(); + $scopeResolver->expects($this->once()) + ->method('getScope') + ->with($scopeCode) + ->willReturn($scopeObject); + $this->scopeResolverPoolMock->expects($this->once()) + ->method('get') + ->with($scope) + ->willReturn($scopeResolver); + + $this->assertTrue($this->model->isValid($scope, $scopeCode)); + } + + public function testIsValidDefault() + { + $this->assertTrue($this->model->isValid(ScopeConfigInterface::SCOPE_TYPE_DEFAULT)); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage The "default" scope can't include a scope code. Try again without entering a scope + */ + public function testNotEmptyScopeCodeForDefaultScope() + { + $this->model->isValid(ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 'some_code'); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Enter a scope before proceeding. + */ + public function testEmptyScope() + { + $this->model->isValid('', 'some_code'); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Enter a scope code before proceeding. + */ + public function testEmptyScopeCode() + { + $this->model->isValid('not_default_scope', ''); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage The scope code can include only lowercase letters (a-z), numbers (0-9) and underscores + */ + public function testWrongScopeCodeFormat() + { + $this->model->isValid('not_default_scope', '123'); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage The "not_default_scope" value doesn't exist. Enter another value. + */ + public function testScopeNotExist() + { + $scope = 'not_default_scope'; + $this->scopeResolverPoolMock->expects($this->once()) + ->method('get') + ->with($scope) + ->willThrowException(new \InvalidArgumentException()); + + $this->model->isValid($scope, 'scope_code'); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage The "not_exist_scope_code" value doesn't exist. Enter another value. + */ + public function testScopeCodeNotExist() + { + $scope = 'not_default_scope'; + $scopeCode = 'not_exist_scope_code'; + + $scopeResolver = $this->getMockBuilder(ScopeResolverInterface::class) + ->getMockForAbstractClass(); + $scopeResolver->expects($this->once()) + ->method('getScope') + ->with($scopeCode) + ->willThrowException(new NoSuchEntityException()); + $this->scopeResolverPoolMock->expects($this->once()) + ->method('get') + ->with($scope) + ->willReturn($scopeResolver); + + $this->model->isValid($scope, $scopeCode); + } +} diff --git a/app/code/Magento/Store/etc/di.xml b/app/code/Magento/Store/etc/di.xml index 48a37dec7d73a9ae9fda07cce9c709b3d0b94284..d3b91a57ef3141fb054239ebd919cb48d77ad5e8 100644 --- a/app/code/Magento/Store/etc/di.xml +++ b/app/code/Magento/Store/etc/di.xml @@ -25,6 +25,7 @@ <preference for="Magento\Framework\App\ScopeFallbackResolverInterface" type="Magento\Store\Model\ScopeFallbackResolver"/> <preference for="Magento\Framework\App\ScopeTreeProviderInterface" type="Magento\Store\Model\ScopeTreeProvider"/> <preference for="Magento\Framework\App\ScopeValidatorInterface" type="Magento\Store\Model\ScopeValidator"/> + <preference for="Magento\Framework\App\Scope\ValidatorInterface" type="Magento\Store\Model\Scope\Validator"/> <type name="Magento\Framework\App\Response\Http"> <plugin name="genericHeaderPlugin" type="Magento\Framework\App\Response\HeaderManager"/> </type> diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php index 5b2698749019e6c983804ac12d14513e5cfc03d0..8181f3c6d80a97859e2a952920029bd7885a9525 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php @@ -6,8 +6,6 @@ */ namespace Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; -use Magento\Catalog\Model\Category; -use Magento\Catalog\Model\Product; use Magento\Framework\Exception\LocalizedException; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; @@ -63,7 +61,7 @@ class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite $model->setEntityType($productId ? self::ENTITY_TYPE_PRODUCT : self::ENTITY_TYPE_CATEGORY) ->setEntityId($productId ?: $categoryId); if ($productId && $categoryId) { - $model->setMetadata(serialize(['category_id' => $categoryId])); + $model->setMetadata(['category_id' => $categoryId]); } } $model->setTargetPath($this->getTargetPath($model)); diff --git a/app/code/Magento/UrlRewrite/Model/MergeDataProvider.php b/app/code/Magento/UrlRewrite/Model/MergeDataProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..20ecf0f52c05781d8acaf076cca745e04481347e --- /dev/null +++ b/app/code/Magento/UrlRewrite/Model/MergeDataProvider.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\UrlRewrite\Model; + +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite as UrlRewriteService; + +/** + * This class is to be used as a container for new generated url rewrites by adding new ones using merge method + * Removes duplicates for a set/array of Url Rewrites based on the unique key of the url_rewrites table + * + */ +class MergeDataProvider +{ + const SEPARATOR = '_'; + + /** + * @var $rewritesArray[] + */ + private $data = []; + + /** + * Adds url rewrites to class data container by removing duplicates by a unique key + * + * @param UrlRewriteService[] $urlRewritesArray + * @return void + */ + public function merge(array $urlRewritesArray) + { + foreach ($urlRewritesArray as $urlRewrite) { + $key = $urlRewrite->getRequestPath() . self::SEPARATOR . $urlRewrite->getStoreId(); + if ($key !== self::SEPARATOR) { + $this->data[$key] = $urlRewrite; + } else { + $this->data[] = $urlRewrite; + } + } + } + + /** + * Returns the data added to container + * + * @return UrlRewriteService[] + */ + public function getData() + { + return $this->data; + } +} diff --git a/app/code/Magento/UrlRewrite/Model/UrlRewrite.php b/app/code/Magento/UrlRewrite/Model/UrlRewrite.php index b8f97ace72936221e2626d3107d89246158f1cb2..e2eaae257b2912c6cfa0178739c70a949d0c06e2 100644 --- a/app/code/Magento/UrlRewrite/Model/UrlRewrite.php +++ b/app/code/Magento/UrlRewrite/Model/UrlRewrite.php @@ -5,6 +5,9 @@ */ namespace Magento\UrlRewrite\Model; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Serialize\Serializer\Json; + /** * @method int getEntityId() * @method string getEntityType() @@ -14,7 +17,6 @@ namespace Magento\UrlRewrite\Model; * @method string getTargetPath() * @method UrlRewrite setEntityId(int $value) * @method UrlRewrite setEntityType(string $value) - * @method UrlRewrite setMetadata($value) * @method UrlRewrite setRequestPath($value) * @method UrlRewrite setTargetPath($value) * @method UrlRewrite setRedirectType($value) @@ -23,6 +25,32 @@ namespace Magento\UrlRewrite\Model; */ class UrlRewrite extends \Magento\Framework\Model\AbstractModel { + /** + * @var Json + */ + private $serializer; + + /** + * UrlRewrite constructor. + * @param \Magento\Framework\Model\Context $context + * @param \Magento\Framework\Registry $registry + * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource + * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection + * @param array $data + * @param Json $serializer + */ + public function __construct( + \Magento\Framework\Model\Context $context, + \Magento\Framework\Registry $registry, + \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, + \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, + array $data = [], + Json $serializer = null + ) { + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); + parent::__construct($context, $registry, $resource, $resourceCollection, $data); + } + /** * Initialize corresponding resource model * @@ -41,6 +69,21 @@ class UrlRewrite extends \Magento\Framework\Model\AbstractModel public function getMetadata() { $metadata = $this->getData(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::METADATA); - return !empty($metadata) ? unserialize($metadata) : []; + return !empty($metadata) ? $this->serializer->unserialize($metadata) : []; + } + + /** + * Overwrite Metadata in the object. + * + * @param array|string $metadata + * + * @return $this + */ + public function setMetadata($metadata) + { + if (is_array($metadata)) { + $metadata = $this->serializer->serialize($metadata); + } + return $this->setData(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::METADATA, $metadata); } } diff --git a/app/code/Magento/UrlRewrite/Service/V1/Data/UrlRewrite.php b/app/code/Magento/UrlRewrite/Service/V1/Data/UrlRewrite.php index 11315279ff219b1a461bfe6aa447a2092cbb3f84..8ed0479a07637fac5cc6f95dfd510c6240e0ad96 100644 --- a/app/code/Magento/UrlRewrite/Service/V1/Data/UrlRewrite.php +++ b/app/code/Magento/UrlRewrite/Service/V1/Data/UrlRewrite.php @@ -6,6 +6,8 @@ namespace Magento\UrlRewrite\Service\V1\Data; use Magento\Framework\Api\AbstractSimpleObject; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Serialize\Serializer\Json; /** * Data abstract class for url storage @@ -37,6 +39,25 @@ class UrlRewrite extends AbstractSimpleObject self::DESCRIPTION => null, ]; + /** + * @var Json + */ + private $serializer; + + /** + * UrlRewrite constructor. + * + * @param array $data + * @param Json $serializer + */ + public function __construct( + $data = [], + Json $serializer = null + ) { + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); + parent::__construct($data); + } + /** * Get data by key * @@ -216,7 +237,7 @@ class UrlRewrite extends AbstractSimpleObject public function getMetadata() { $metadata = $this->_get(self::METADATA); - return !empty($metadata) ? unserialize($metadata) : []; + return !empty($metadata) ? $this->serializer->unserialize($metadata) : []; } /** @@ -227,7 +248,7 @@ class UrlRewrite extends AbstractSimpleObject public function setMetadata($metadata) { if (is_array($metadata)) { - $metadata = serialize($metadata); + $metadata = $this->serializer->serialize($metadata); } return $this->setData(UrlRewrite::METADATA, $metadata); } diff --git a/app/code/Magento/UrlRewrite/Setup/UpgradeData.php b/app/code/Magento/UrlRewrite/Setup/UpgradeData.php new file mode 100644 index 0000000000000000000000000000000000000000..ad71639398e1b71a83dcb8c869739f2a40079945 --- /dev/null +++ b/app/code/Magento/UrlRewrite/Setup/UpgradeData.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\UrlRewrite\Setup; + +use Magento\Framework\DB\FieldDataConverterFactory; +use Magento\Framework\DB\DataConverter\SerializedToJson; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\UpgradeDataInterface; + +class UpgradeData implements UpgradeDataInterface +{ + /** + * @var FieldDataConverterFactory + */ + private $fieldDataConverterFactory; + + /** + * Constructor + * + * @param FieldDataConverterFactory $fieldDataConverterFactory + */ + public function __construct( + FieldDataConverterFactory $fieldDataConverterFactory + ) { + $this->fieldDataConverterFactory = $fieldDataConverterFactory; + } + + /** + * @inheritdoc + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), '2.0.1', '<')) { + $this->convertSerializedDataToJson($setup); + } + + $setup->endSetup(); + } + + /** + * Convert metadata from serialized to JSON format: + * + * @param ModuleDataSetupInterface $setup + * + * @return void + */ + public function convertSerializedDataToJson($setup) + { + $fieldDataConverter = $this->fieldDataConverterFactory->create(SerializedToJson::class); + $fieldDataConverter->convert( + $setup->getConnection(), + $setup->getTable('url_rewrite'), + 'url_rewrite_id', + 'metadata' + ); + } +} diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Block/Plugin/Store/Switcher/SetRedirectUrlTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Block/Plugin/Store/Switcher/SetRedirectUrlTest.php index 9cf18a76cd045c8abd75d9f81c533d045df5f8ff..5118d12fad6c6e6b069a5a080c412ec1fd026e88 100644 --- a/app/code/Magento/UrlRewrite/Test/Unit/Block/Plugin/Store/Switcher/SetRedirectUrlTest.php +++ b/app/code/Magento/UrlRewrite/Test/Unit/Block/Plugin/Store/Switcher/SetRedirectUrlTest.php @@ -81,7 +81,7 @@ class SetRedirectUrlTest extends \PHPUnit_Framework_TestCase public function testGetTargetStorePostData() { - $urlRewrite = $this->getMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class); + $urlRewrite = $this->getMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class, [], [], '', false); $urlRewrite->expects($this->once())->method('getRequestPath')->willReturn('path'); $this->request->expects($this->once())->method('getPathInfo')->willReturn('path'); diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Model/MergeDataProviderTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Model/MergeDataProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..76aa5d368be4c805155b374245799197e4b538dd --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Unit/Model/MergeDataProviderTest.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\UrlRewrite\Test\Unit\Model; + +use Magento\UrlRewrite\Model\MergeDataProvider; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class MergeDataProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var MergeDataProvider|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlRewritesSet; + + /** + * Set up + * + * @return void + */ + protected function setUp() + { + $this->urlRewritesSet = (new ObjectManager($this))->getObject( + MergeDataProvider::class, + [] + ); + } + + /** + * Run test merge method + * + * @param array $urlRewriteMockArray + * @param String $expectedData + * @param int $arrayCount + * @dataProvider mergeDataProvider + * @return void + */ + public function testMerge($urlRewriteMockArray, $expectedData, $arrayCount) + { + $this->urlRewritesSet->merge($urlRewriteMockArray); + $this->assertEquals($expectedData, $this->urlRewritesSet->getData()); + $this->assertCount($arrayCount, $this->urlRewritesSet->getData()); + } + + /** + * Run test getData method when data is Empty + * + * @return void + */ + public function testGetDataWhenEmpty() + { + $this->assertEmpty($this->urlRewritesSet->getData()); + } + + /** + * Data provider for testMerge + * + * @return array + */ + public function mergeDataProvider() + { + $urlRewriteMock1 = $this->getMock(UrlRewrite::class, [], [], '', false); + + $requestPathForMock2 = 'magento.tst/products/simpleproduct2'; + $storeIdForMock2 = 'testStore2'; + $urlRewriteMock2 = $this->getMock(UrlRewrite::class, [], [], '', false); + + $urlRewriteMock2->expects($this->atLeastOnce()) + ->method('getRequestPath') + ->willReturn($requestPathForMock2); + + $urlRewriteMock2->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeIdForMock2); + + $requestPathForMock3 = 'magento.tst/products/simpleproduct3'; + $storeIdForMock3 = 'testStore3'; + $urlRewriteMock3 = $this->getMock(UrlRewrite::class, [], [], '', false); + + $urlRewriteMock3->expects($this->atLeastOnce()) + ->method('getRequestPath') + ->willReturn($requestPathForMock3); + + $urlRewriteMock3->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeIdForMock3); + + return [ + [ + [], + [], + 0 + ], + [ + [$urlRewriteMock1], + [$urlRewriteMock1], + 1 + ], + [ + [ + $urlRewriteMock1, + $urlRewriteMock2, + $urlRewriteMock2 + ], + [ + $urlRewriteMock1, + $requestPathForMock2 . '_' . $storeIdForMock2 => $urlRewriteMock2 + ], + 2 + ], + [ + [ + $urlRewriteMock1, + $urlRewriteMock2, + $urlRewriteMock3 + ], + [ + $urlRewriteMock1, + $requestPathForMock2 . '_' . $storeIdForMock2 => $urlRewriteMock2, + $requestPathForMock3 . '_' . $storeIdForMock3 => $urlRewriteMock3 + ], + 3 + ], + ]; + } +} diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Model/UrlRewriteTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Model/UrlRewriteTest.php new file mode 100644 index 0000000000000000000000000000000000000000..db536c5aa38b3622db9c5cc2d9a4d07348e7fae6 --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Unit/Model/UrlRewriteTest.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\UrlRewrite\Test\Unit\Model; + +class UrlRewriteTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\UrlRewrite\Model\UrlRewrite + */ + protected $model; + + protected function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $context = $this->getMock(\Magento\Framework\Model\Context::class, [], [], '', false); + $registry = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); + $resource = $this->getMock( + \Magento\Framework\Model\ResourceModel\AbstractResource::class, + ['getIdFieldName', '_construct', 'getConnection'], + [], + '', + false + ); + $resourceCollection = $this->getMock(\Magento\Framework\Data\Collection\AbstractDb::class, [], [], '', false); + $serializer = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class, [], [], '', false); + $serializer->expects($this->any()) + ->method('serialize') + ->willReturnCallback( + function ($value) { + return json_encode($value); + } + ); + $serializer->expects($this->any()) + ->method('unserialize') + ->willReturnCallback( + function ($value) { + return json_decode($value, true); + } + ); + + $this->model = $objectManager->getObject( + \Magento\UrlRewrite\Model\UrlRewrite::class, + [ + 'context' => $context, + 'registry' => $registry, + 'resource' => $resource, + 'resourceCollection' => $resourceCollection, + 'data' => [], + 'serializer' => $serializer, + ] + ); + } + + public function testSetAndGetMetadata() + { + $testData = [1, 2, 3]; + + $this->model->setMetadata($testData); + + $this->assertEquals($testData, $this->model->getMetadata()); + } +} diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Service/V1/Data/UrlRewriteTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Service/V1/Data/UrlRewriteTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bce4505b2eda2ecbc09c5e7b33173c2153703bb2 --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Unit/Service/V1/Data/UrlRewriteTest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\UrlRewrite\Test\Unit\Service\V1\Data; + +class UrlRewriteTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\UrlRewrite\Model\UrlRewrite + */ + protected $model; + + protected function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $serializer = $this->getMock(\Magento\Framework\Serialize\Serializer\Json::class, [], [], '', false); + $serializer->expects($this->any()) + ->method('serialize') + ->willReturnCallback( + function ($value) { + return json_encode($value); + } + ); + $serializer->expects($this->any()) + ->method('unserialize') + ->willReturnCallback( + function ($value) { + return json_decode($value, true); + } + ); + + $this->model = $objectManager->getObject( + \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class, + [ + 'data' => [], + 'serializer' => $serializer, + ] + ); + } + + public function testSetAndGetMetadata() + { + $testData = [1, 2, 3]; + + $this->model->setMetadata($testData); + + $this->assertEquals($testData, $this->model->getMetadata()); + } +} diff --git a/app/code/Magento/UrlRewrite/etc/module.xml b/app/code/Magento/UrlRewrite/etc/module.xml index 284c67950ac539e6d4f030c32611981278f51c14..499a5166a860691e10fb7214b5a8983dd0d0ea3a 100644 --- a/app/code/Magento/UrlRewrite/etc/module.xml +++ b/app/code/Magento/UrlRewrite/etc/module.xml @@ -6,6 +6,6 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_UrlRewrite" setup_version="2.0.0"> + <module name="Magento_UrlRewrite" setup_version="2.0.1"> </module> </config> diff --git a/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php b/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php index 837b7f7135d29d22580aaf1103b9a5c3ae2579d2..c23a8019e37eeb3ebebd8662aea23a773dff036e 100644 --- a/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php +++ b/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php @@ -67,7 +67,6 @@ class ContainerTest extends AbstractContainerTest . '<option value="content.top" >Main Content Top</option></select>'; $this->eventManagerMock->expects($this->exactly(2))->method('dispatch')->willReturn(true); - $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false); $this->themeCollectionFactoryMock->expects($this->once()) ->method('create') @@ -154,7 +153,6 @@ class ContainerTest extends AbstractContainerTest . '<option value="sidebar.main" >Sidebar Main</option></select>'; $this->eventManagerMock->expects($this->exactly(2))->method('dispatch')->willReturn(true); - $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false); $this->themeCollectionFactoryMock->expects($this->once()) ->method('create') @@ -285,7 +283,6 @@ class ContainerTest extends AbstractContainerTest . '<option value="sidebar.main" >Sidebar Main</option></select>'; $this->eventManagerMock->expects($this->exactly(2))->method('dispatch')->willReturn(true); - $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false); $this->themeCollectionFactoryMock->expects($this->once()) ->method('create') @@ -403,7 +400,6 @@ class ContainerTest extends AbstractContainerTest . 'Sidebar Main</option></select>'; $this->eventManagerMock->expects($this->exactly(2))->method('dispatch')->willReturn(true); - $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false); $this->themeCollectionFactoryMock->expects($this->once()) ->method('create') diff --git a/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php b/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php index b9dcc8d6fe73f500b1d314b90bf992c672fe14cf..e9c22670b1a104a46aa30525c70593ee40bdf81f 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php @@ -136,7 +136,6 @@ class WishlistTest extends \PHPUnit_Framework_TestCase ); $customerServiceMock = $this->getMock(\Magento\Customer\Api\Data\CustomerInterface::class, [], [], '', false); $wishlistSharingUrl = 'wishlist/shared/index/1'; - $locale = 'en_US'; $productUrl = 'http://product.url/'; $productName = 'Product name'; @@ -160,25 +159,6 @@ class WishlistTest extends \PHPUnit_Framework_TestCase $this->urlBuilderMock->expects($this->once()) ->method('getUrl') ->will($this->returnValue($wishlistSharingUrl)); - $this->scopeConfig->expects($this->any()) - ->method('getValue') - ->will($this->returnValueMap( - [ - [ - 'advanced/modules_disable_output/Magento_Rss', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - null, - null, - ], - [ - Data::XML_PATH_DEFAULT_LOCALE, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - null, - $locale - ], - ] - ) - ); $staticArgs = [ 'productName' => $productName, diff --git a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less index eb7f5781976adeead4d2768c71f1eb52f7017e59..a9028fa65cebf704facd24caa5f8ba9b81956c4a 100644 --- a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less @@ -492,15 +492,23 @@ .block-addresses-list { .items.addresses { &:extend(.abs-add-clearfix-desktop all); + font-size: 0; > .item { - &:extend(.abs-blocks-2columns all); + display: inline-block; + font-size: @font-size__base; margin-bottom: @indent__base; + vertical-align: top; + width: 48.8%; &:nth-last-child(1), &:nth-last-child(2) { margin-bottom: 0; } + + &:nth-child(even) { + margin-left: 2.4%; + } } } } diff --git a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less index 74c7b8d3f2970dac9edea2e8c2c4a98e798e83f2..a9b42a82036bc70abf4eefb883c187e5417ff198 100644 --- a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less @@ -106,6 +106,8 @@ .box-billing-address, .box-shipping-address, + .box-address-billing, + .box-address-shipping, .box-information, .box-newsletter { .box-content { @@ -437,15 +439,23 @@ .block-addresses-list { .items.addresses { &:extend(.abs-add-clearfix-desktop all); + font-size: 0; > .item { - &:extend(.abs-blocks-2columns all); + display: inline-block; + font-size: @font-size__base; margin-bottom: @indent__base; + vertical-align: top; + width: 48%; &:nth-last-child(1), &:nth-last-child(2) { margin-bottom: 0; } + + &:nth-child(even) { + margin-left: 4%; + } } } @@ -479,6 +489,8 @@ .box-billing-address, .box-shipping-address, + .box-address-billing, + .box-address-shipping, .box-information { .box-content { &:extend(.abs-account-block-font-size all); diff --git a/app/etc/di.xml b/app/etc/di.xml index 3a5533fd825ce677d5df3f6ff2dded69af010693..a7c38ae1246b602a105c5e1a588fc5e7b9561b2e 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1221,4 +1221,17 @@ </argument> </arguments> </type> + <type name="Magento\Framework\DB\TemporaryTableService"> + <arguments> + <argument name="allowedIndexMethods" xsi:type="array"> + <item name="HASH" xsi:type="string">HASH</item> + <item name="BTREE" xsi:type="string">BTREE</item> + </argument> + <argument name="allowedEngines" xsi:type="array"> + <item name="INNODB" xsi:type="string">INNODB</item> + <item name="MEMORY" xsi:type="string">MEMORY</item> + <item name="MYISAM" xsi:type="string">MYISAM</item> + </argument> + </arguments> + </type> </config> diff --git a/bin/magento b/bin/magento index 1cb700def7fa7f3faf073dbd7bb0300892b06332..3fa9221763cf5fca0a09eb4bc6f43d3a5cb22a42 100755 --- a/bin/magento +++ b/bin/magento @@ -28,5 +28,5 @@ try { echo "\n\n"; $e = $e->getPrevious(); } - exit(Cli::RETURN_FAILURE); + exit(Magento\Framework\Console\Cli::RETURN_FAILURE); } diff --git a/composer.json b/composer.json index 11fe00b7ed45add98a8996bea8a480403d44a53c..16c73d23ef6d8a743ed69cfa53e890e8e2642a7f 100644 --- a/composer.json +++ b/composer.json @@ -226,7 +226,7 @@ "psr-0": { "": [ "app/code/", - "var/generation" + "generated/code" ] }, "files": [ diff --git a/composer.lock b/composer.lock index e69e12953fea2c150e1e063ddb9502148aad38ec..43c927cb95a1d5eb07b272dda0b018be12c73fbe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "01335ef9c76fd6d77bf6bd9236b84d03", + "hash": "e5946b0500f46fab5f05e46957258ed4", "content-hash": "07be52ec4880d0390acff2dd605a194d", "packages": [ { @@ -554,16 +554,16 @@ }, { "name": "magento/magento-composer-installer", - "version": "0.1.11", + "version": "0.1.12", "source": { "type": "git", "url": "https://github.com/magento/magento-composer-installer.git", - "reference": "a12b9577cd9859af67d2365ae38d23ddfc57cf4d" + "reference": "10c600e88ad34fec71bb6b435ea8415ce92d51de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/a12b9577cd9859af67d2365ae38d23ddfc57cf4d", - "reference": "a12b9577cd9859af67d2365ae38d23ddfc57cf4d", + "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/10c600e88ad34fec71bb6b435ea8415ce92d51de", + "reference": "10c600e88ad34fec71bb6b435ea8415ce92d51de", "shasum": "" }, "require": { @@ -629,7 +629,7 @@ "composer-installer", "magento" ], - "time": "2016-06-15 04:02:25" + "time": "2016-10-06 16:05:07" }, { "name": "magento/zendframework1", @@ -1687,7 +1687,7 @@ }, { "name": "zendframework/zend-code", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-code.git", @@ -1740,7 +1740,7 @@ }, { "name": "zendframework/zend-config", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-config.git", @@ -1797,7 +1797,7 @@ }, { "name": "zendframework/zend-console", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-console.git", @@ -1847,7 +1847,7 @@ }, { "name": "zendframework/zend-crypt", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-crypt.git", @@ -1899,7 +1899,7 @@ }, { "name": "zendframework/zend-di", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-di.git", @@ -1950,7 +1950,7 @@ }, { "name": "zendframework/zend-escaper", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-escaper.git", @@ -1995,7 +1995,7 @@ }, { "name": "zendframework/zend-eventmanager", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-eventmanager.git", @@ -2041,7 +2041,7 @@ }, { "name": "zendframework/zend-filter", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-filter.git", @@ -2097,7 +2097,7 @@ }, { "name": "zendframework/zend-form", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-form.git", @@ -2168,7 +2168,7 @@ }, { "name": "zendframework/zend-http", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-http.git", @@ -2219,7 +2219,7 @@ }, { "name": "zendframework/zend-i18n", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-i18n.git", @@ -2283,7 +2283,7 @@ }, { "name": "zendframework/zend-inputfilter", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-inputfilter.git", @@ -2334,7 +2334,7 @@ }, { "name": "zendframework/zend-json", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-json.git", @@ -2388,7 +2388,7 @@ }, { "name": "zendframework/zend-loader", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-loader.git", @@ -2433,7 +2433,7 @@ }, { "name": "zendframework/zend-log", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-log.git", @@ -2495,7 +2495,7 @@ }, { "name": "zendframework/zend-math", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-math.git", @@ -2546,7 +2546,7 @@ }, { "name": "zendframework/zend-modulemanager", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-modulemanager.git", @@ -2604,7 +2604,7 @@ }, { "name": "zendframework/zend-mvc", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-mvc.git", @@ -2692,7 +2692,7 @@ }, { "name": "zendframework/zend-serializer", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-serializer.git", @@ -2745,7 +2745,7 @@ }, { "name": "zendframework/zend-server", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-server.git", @@ -2792,7 +2792,7 @@ }, { "name": "zendframework/zend-servicemanager", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-servicemanager.git", @@ -2842,7 +2842,7 @@ }, { "name": "zendframework/zend-soap", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-soap.git", @@ -2894,7 +2894,7 @@ }, { "name": "zendframework/zend-stdlib", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-stdlib.git", @@ -2949,7 +2949,7 @@ }, { "name": "zendframework/zend-text", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-text.git", @@ -2996,7 +2996,7 @@ }, { "name": "zendframework/zend-uri", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-uri.git", @@ -3044,7 +3044,7 @@ }, { "name": "zendframework/zend-validator", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-validator.git", @@ -3109,7 +3109,7 @@ }, { "name": "zendframework/zend-view", - "version": "2.4.10", + "version": "2.4.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-view.git", diff --git a/dev/tests/functional/.htaccess.sample b/dev/tests/functional/.htaccess.sample index f1e60cf9b67e91779153e4c9d9f780c8f7bc9438..c1fcc7c278c8aa0e79bd372c89ed6ef6e8ec2f0f 100644 --- a/dev/tests/functional/.htaccess.sample +++ b/dev/tests/functional/.htaccess.sample @@ -1,6 +1,6 @@ ############################################## ## Allow access to command.php and website.php - <FilesMatch "command.php|website.php|export.php"> + <FilesMatch "command.php|website.php|export.php|pathChecker.php|deleteMagentoGeneratedCode.php"> order allow,deny allow from all </FilesMatch> diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Setup.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Setup.php index 385c11cd74f82e4adbc78f5e26b14e1e03931dae..5f5fe66d96cef6cc25689b315a31d91214e01719 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Setup.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Setup.php @@ -18,6 +18,11 @@ class Setup extends Cli */ const PARAM_SETUP_UNINSTALL = 'setup:uninstall'; + /** + * Parameter for DI compile Magento command. + */ + const PARAM_SETUP_DI_COMPILE = 'setup:di:compile'; + /** * Options for uninstall Magento command. * @@ -34,4 +39,14 @@ class Setup extends Cli { parent::execute(Setup::PARAM_SETUP_UNINSTALL, $this->options); } + + /** + * DI Compile. + * + * @return void + */ + public function diCompile() + { + parent::execute(Setup::PARAM_SETUP_DI_COMPILE); + } } diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php new file mode 100644 index 0000000000000000000000000000000000000000..c82231a9e38f1a10dab7cc822bb4bed2f092cb2d --- /dev/null +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Mtf\Util\Command; + +use Magento\Mtf\Util\Protocol\CurlInterface; +use Magento\Mtf\Util\Protocol\CurlTransport; + +/** + * GeneratedCode removes generated code of Magento (like generated/code and generated/metadata). + */ +class GeneratedCode +{ + /** + * Url to deleteMagentoGeneratedCode.php. + */ + const URL = 'dev/tests/functional/utils/deleteMagentoGeneratedCode.php'; + + /** + * Curl transport protocol. + * + * @var CurlTransport + */ + private $transport; + + /** + * @param CurlTransport $transport + */ + public function __construct(CurlTransport $transport) + { + $this->transport = $transport; + } + + /** + * Remove generated code. + * + * @return void + */ + public function delete() + { + $url = $_ENV['app_frontend_url'] . self::URL; + $curl = $this->transport; + $curl->write($url, [], CurlInterface::GET); + $curl->read(); + $curl->close(); + } +} diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php new file mode 100644 index 0000000000000000000000000000000000000000..f61210dc554efc910d4d44b2f5f7bed875d4955d --- /dev/null +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Mtf\Util\Command; + +use Magento\Mtf\Util\Protocol\CurlInterface; +use Magento\Mtf\Util\Protocol\CurlTransport; + +/** + * PathChecker checks that path to file or directory exists. + */ +class PathChecker +{ + /** + * Url to checkPath.php. + */ + const URL = 'dev/tests/functional/utils/pathChecker.php'; + + /** + * Curl transport protocol. + * + * @var CurlTransport + */ + private $transport; + + /** + * @param CurlTransport $transport + */ + public function __construct(CurlTransport $transport) + { + $this->transport = $transport; + } + + /** + * Check that $path exists. + * + * @param string $path + * @return bool + */ + public function pathExists($path) + { + $url = $_ENV['app_frontend_url'] . self::URL . '?path=' . urlencode($path); + $curl = $this->transport; + $curl->write($url, [], CurlInterface::GET); + $result = $curl->read(); + $curl->close(); + + return strpos($result, 'path exists: true') !== false; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml index 435c1b568827147859c493203bd68186ed0d8443..a569274f6121598a23afdb3aa72451b965d9953d 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\AddToCartCrossSellTest" summary="Promote Products as Cross-Sells" ticketId="MAGETWO-12390"> <variation name="AddToCartCrossSellTestVariation1" method="test"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, stable:no</data> <data name="products" xsi:type="string">simple1::catalogProductSimple::product_with_category,simple2::catalogProductSimple::product_with_category,config1::configurableProduct::two_options_with_fixed_price</data> <data name="promotedProducts" xsi:type="string">simple1:simple2,config1;config1:simple2</data> <data name="navigateProductsOrder" xsi:type="string">simple1,config1,simple2</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateRelatedProductsTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateRelatedProductsTest.xml index a88fb5d6a27ce8720b8df3b0523bc5c08cade8f6..f3b17309e045d37eb95b9fc7a074af4deba60063 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateRelatedProductsTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateRelatedProductsTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\NavigateRelatedProductsTest" summary="Promote Products as Related" ticketId="MAGETWO-12392"> <variation name="NavigateRelatedProductsTestVariation1" method="test"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, stable:no</data> <data name="products" xsi:type="string">simple1::catalogProductSimple::product_with_category,simple2::catalogProductSimple::product_with_category,config1::configurableProduct::two_options_with_fixed_price</data> <data name="selectable" xsi:type="string">simple1:yes,simple2:yes,config1:no</data> <data name="promotedProducts" xsi:type="string">simple1:simple2,config1;config1:simple2</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml index 045b6ae009e7b4c91124069cf54bc980a3ae2292..6e309c4932331cc45ec22db9f1e4a1750fa773a7 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\NavigateUpSellProductsTest" summary="Promote Products as Up-Sells" ticketId="MAGETWO-12391"> <variation name="NavigateUpSellProductsTestVariation1" method="test"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, stable:no</data> <data name="products" xsi:type="string">simple1::catalogProductSimple::product_with_category,simple2::catalogProductSimple::product_with_category,config1::configurableProduct::two_options_with_fixed_price</data> <data name="promotedProducts" xsi:type="string">simple1:simple2,config1;config1:simple2</data> <data name="navigateProductsOrder" xsi:type="string">simple1,config1,simple2</data> diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertGenerationFilePathCheck.php b/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertGenerationFilePathCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..64e46ac9b0289f6d0ee265339eff208c35fe7c3f --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertGenerationFilePathCheck.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Install\Test\Constraint; + +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Mtf\Util\Command\PathChecker; + +/** + * Assert that path of generated files is correct. + */ +class AssertGenerationFilePathCheck extends AbstractConstraint +{ + /** + * Assert that path of generated files is correct. + * + * @param PathChecker $pathChecker + * @return void + */ + public function processAssert(PathChecker $pathChecker) + { + $existsPaths = [ + 'generated/code', + 'generated/metadata', + 'generated/metadata/global.ser', + 'generated/metadata/adminhtml.ser', + 'generated/metadata/crontab.ser', + 'generated/metadata/frontend.ser', + 'generated/metadata/webapi_rest.ser', + 'generated/metadata/webapi_soap.ser', + ]; + + $nonExistsPaths = [ + 'var/di', + 'var/generation' + ]; + + foreach ($existsPaths as $path) { + \PHPUnit_Framework_Assert::assertTrue( + $pathChecker->pathExists($path), + 'Path "' . $path . '" does not exist.' + ); + } + + foreach ($nonExistsPaths as $path) { + \PHPUnit_Framework_Assert::assertFalse( + $pathChecker->pathExists($path), + 'Path "' . $path . '" exists.' + ); + } + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Path of generated files is correct.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php index 657ea4379b46e8d106e93cdddfc978c7c4d7159a..32594facd3a1bce7a6ae59f04840c8fd8e0e0421 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php +++ b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php @@ -17,6 +17,7 @@ use Magento\Install\Test\Constraint\AssertSuccessfulReadinessCheck; use Magento\Install\Test\Constraint\AssertAdminUriAutogenerated; use Magento\Install\Test\Constraint\AssertDevdocsLink; use Magento\Mtf\Util\Command\Cli\Setup; +use Magento\Mtf\Util\Command\GeneratedCode; use Magento\Mtf\Client\BrowserInterface; /** @@ -76,6 +77,13 @@ class InstallTest extends Injectable */ protected $installPage; + /** + * Setup Magento for tests executions. + * + * @var Setup + */ + private $magentoSetup; + /** * Uninstall Magento before test. * @@ -101,12 +109,17 @@ class InstallTest extends Injectable * @param Install $installPage * @param Setup $magentoSetup * @param DevdocsInstall $devdocsInstallPage + * @param GeneratedCode $generatedCode * @return void */ - public function __inject(Install $installPage, Setup $magentoSetup, DevdocsInstall $devdocsInstallPage) - { - // Uninstall Magento. - $magentoSetup->uninstall(); + public function __inject( + Install $installPage, + Setup $magentoSetup, + DevdocsInstall $devdocsInstallPage, + GeneratedCode $generatedCode + ) { + $generatedCode->delete(); + $this->magentoSetup = $magentoSetup; $this->installPage = $installPage; $this->devdocsInstallPage = $devdocsInstallPage; } @@ -122,8 +135,10 @@ class InstallTest extends Injectable * @param AssertAdminUriAutogenerated $assertAdminUri * @param AssertDevdocsLink $assertDevdocsLink * @param BrowserInterface $browser + * @param bool $diCompile * @param array $install [optional] * @return array + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function test( User $user, @@ -134,8 +149,10 @@ class InstallTest extends Injectable AssertAdminUriAutogenerated $assertAdminUri, AssertDevdocsLink $assertDevdocsLink, BrowserInterface $browser, + $diCompile = false, array $install = [] ) { + $this->magentoSetup->uninstall(); $dataConfig = array_merge($install, $configData); if (isset($dataConfig['httpsFront'])) { $dataConfig['https'] = str_replace('http', 'https', $dataConfig['baseUrl']); @@ -178,6 +195,10 @@ class InstallTest extends Injectable // Step 6: Install. $this->installPage->getInstallBlock()->clickInstallNow(); + if ($diCompile) { + $this->magentoSetup->diCompile(); + } + return ['installConfig' => $installConfig]; } diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml index cdfc018346b56dad265044131f6719fb3c48ef78..cb785723763be541be0136a40cb12d3c6eecb558 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml +++ b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.xml @@ -48,10 +48,12 @@ <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" /> <constraint name="Magento\Install\Test\Constraint\AssertSecureUrlEnabled" /> </variation> - <variation name="InstallTestVariation6" summary="Install with default values"> + <variation name="InstallTestVariation6" summary="Install with default values and check DI compile" ticketId="MAGETWO-62817"> <data name="user/dataset" xsi:type="string">default</data> + <data name="diCompile" xsi:type="boolean">true</data> <constraint name="Magento\Install\Test\Constraint\AssertSuccessInstall" /> <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" /> + <constraint name="Magento\Install\Test\Constraint\AssertGenerationFilePathCheck" /> </variation> </testCase> </config> diff --git a/dev/tests/functional/utils/deleteMagentoGeneratedCode.php b/dev/tests/functional/utils/deleteMagentoGeneratedCode.php new file mode 100644 index 0000000000000000000000000000000000000000..b90269a9a21f68a8f2601eb409a4c7c5e912ff87 --- /dev/null +++ b/dev/tests/functional/utils/deleteMagentoGeneratedCode.php @@ -0,0 +1,7 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +exec('rm -rf ../../../../generated/*'); diff --git a/dev/tests/functional/utils/pathChecker.php b/dev/tests/functional/utils/pathChecker.php new file mode 100644 index 0000000000000000000000000000000000000000..a57ac8d3e07b1d82f777e64dddba3518cf281a94 --- /dev/null +++ b/dev/tests/functional/utils/pathChecker.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +if (isset($_GET['path'])) { + $path = urldecode($_GET['path']); + + if (file_exists('../../../../' . $path)) { + echo 'path exists: true'; + } else { + echo 'path exists: false'; + } +} else { + throw new \InvalidArgumentException("GET parameter 'path' is not set."); +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Application.php b/dev/tests/integration/framework/Magento/TestFramework/Application.php index 3d76b9774d50976e1f74cab4979f39e1cbd86064..e52976f505710a226e4cf008b8e21341a08fecce 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Application.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Application.php @@ -630,12 +630,13 @@ class Application { $path = DirectoryList::PATH; $var = "{$this->installDir}/var"; + $generated = "{$this->installDir}/generated"; $customDirs = [ DirectoryList::CONFIG => [$path => "{$this->installDir}/etc"], DirectoryList::VAR_DIR => [$path => $var], DirectoryList::MEDIA => [$path => "{$this->installDir}/pub/media"], DirectoryList::STATIC_VIEW => [$path => "{$this->installDir}/pub/static"], - DirectoryList::GENERATION => [$path => "{$var}/generation"], + DirectoryList::GENERATION => [$path => "{$generated}/code"], DirectoryList::CACHE => [$path => "{$var}/cache"], DirectoryList::LOG => [$path => "{$var}/log"], DirectoryList::SESSION => [$path => "{$var}/session"], diff --git a/dev/tests/integration/testsuite/Magento/Backend/Block/TemplateTest.php b/dev/tests/integration/testsuite/Magento/Backend/Block/TemplateTest.php index 2a3441afcd2aad6d1eeab87a1c4c3e7f7c266bd2..ea450cbe81c2ff5bc4ad8a28705dc7ba97a84a82 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Block/TemplateTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Block/TemplateTest.php @@ -34,26 +34,4 @@ class TemplateTest extends \PHPUnit_Framework_TestCase { $this->assertGreaterThan(15, strlen($this->_block->getFormKey())); } - - /** - * @magentoAppArea adminhtml - * @covers \Magento\Backend\Block\Template::isOutputEnabled - * @magentoConfigFixture current_store advanced/modules_disable_output/dummy 1 - */ - public function testIsOutputEnabledTrue() - { - $this->_block->setData('module_name', 'dummy'); - $this->assertFalse($this->_block->isOutputEnabled('dummy')); - } - - /** - * @magentoAppArea adminhtml - * @covers \Magento\Backend\Block\Template::isOutputEnabled - * @magentoConfigFixture current_store advanced/modules_disable_output/dummy 0 - */ - public function testIsOutputEnabledFalse() - { - $this->_block->setData('module_name', 'dummy'); - $this->assertTrue($this->_block->isOutputEnabled('dummy')); - } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserverTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..101ce61c779dcfd930a7816bcafc37c84c4d3fdc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserverTest.php @@ -0,0 +1,188 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogUrlRewrite\Observer; + +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; + +/** + * Test Cases: + * + * - has changes for url_key, is_anchor, changed product list + * - in global scope + * - in store + * - generate canonical + * - generate children + * - has children + * - generate current + * - has rewrites history + * @magentoAppArea adminhtml + */ +class CategoryProcessUrlRewriteSavingObserverTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Magento\Framework\ObjectManagerInterface */ + protected $objectManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @param array $filter + * @return array + */ + private function getActualResults(array $filter) + { + /** @var \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder */ + $urlFinder = $this->objectManager->get(\Magento\UrlRewrite\Model\UrlFinderInterface::class); + $actualResults = []; + foreach ($urlFinder->findAllByData($filter) as $url) { + $actualResults[] = [ + 'request_path' => $url->getRequestPath(), + 'target_path' => $url->getTargetPath(), + 'is_auto_generated' => (int)$url->getIsAutogenerated(), + 'redirect_type' => $url->getRedirectType() + ]; + } + return $actualResults; + } + + public function tearDown() + { + $category = $this->objectManager->create(\Magento\Catalog\Model\Category::class); + $category->load(3); + $category->delete(); + } + + /** + * @magentoDataFixture Magento/CatalogUrlRewrite/_files/categories.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testUrlKeyHasChanged() + { + $categoryFilter = [ + UrlRewrite::ENTITY_TYPE => 'category', + ]; + $expected = [ + [ + 'request_path' => "category-1.html", + 'target_path' => "catalog/category/view/id/3", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ], + [ + 'request_path' => "category-1/category-1-1.html", + 'target_path' => "catalog/category/view/id/4", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ], + [ + 'request_path' => "category-1/category-1-1/category-1-1-1.html", + 'target_path' => "catalog/category/view/id/5", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ], + [ + 'request_path' => "category-2.html", + 'target_path' => "catalog/category/view/id/6", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ] + + ]; + $actual = $this->getActualResults($categoryFilter); + foreach ($expected as $row) { + $this->assertContains($row, $actual); + } + /** @var \Magento\Catalog\Model\Category $category */ + $category = $this->objectManager->create(\Magento\Catalog\Model\Category::class); + $category->load(3); + $category->setData('save_rewrites_history', false); + $category->setUrlKey('new-url'); + $category->save(); + $expected = [ + [ + 'request_path' => "new-url.html", + 'target_path' => "catalog/category/view/id/3", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ], + [ + 'request_path' => "new-url/category-1-1.html", + 'target_path' => "catalog/category/view/id/4", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ], + [ + 'request_path' => "new-url/category-1-1/category-1-1-1.html", + 'target_path' => "catalog/category/view/id/5", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ], + [ + 'request_path' => "category-2.html", + 'target_path' => "catalog/category/view/id/6", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ] + + ]; + $actual = $this->getActualResults($categoryFilter); + foreach ($expected as $row) { + $this->assertContains($row, $actual); + } + } + + /** + * @magentoDataFixture Magento/CatalogUrlRewrite/_files/categories_with_products.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testIsAnchorHasChanged() + { + $categoryFilter = [ + UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE, + ]; + /** @var \Magento\Catalog\Model\Category $category */ + $category = $this->objectManager->create(\Magento\Catalog\Model\Category::class); + $category->load(3); + $category->setData('is_anchor', false); + $category->save(); + $expected = [ + [ + 'request_path' => "category-1.html", + 'target_path' => "catalog/category/view/id/3", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ], + [ + 'request_path' => "category-1/category-1-1.html", + 'target_path' => "catalog/category/view/id/4", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ], + [ + 'request_path' => "category-1/category-1-1/category-1-1-1.html", + 'target_path' => "catalog/category/view/id/5", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ], + [ + 'request_path' => "category-2.html", + 'target_path' => "catalog/category/view/id/6", + 'is_auto_generated' => 1, + 'redirect_type' => 0 + ] + + ]; + $actual = $this->getActualResults($categoryFilter); + foreach ($expected as $row) { + $this->assertContains($row, $actual); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Address/Renderer/DefaultRendererTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/Renderer/DefaultRendererTest.php index cdc31a9199b6a49fbe92ff19c0298c18c64c15e5..14e4829ca9683b0a3ec875afa2aeeaf1e9d6c274 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Address/Renderer/DefaultRendererTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/Renderer/DefaultRendererTest.php @@ -56,7 +56,7 @@ class DefaultRendererTest extends \PHPUnit_Framework_TestCase [ $addressAttributes, AttributeDataFactory::OUTPUT_FORMAT_PDF, - "John Smith|\n\nGreen str, 67\n\n\n\n\nCityM,|\nAlabama, 75477|\nUnited States|\nT: 3468676|\n|\n|" + "John Smith|\n\nGreen str, 67|\n\n\n\nCityM, Alabama, 75477|\nUnited States|\nT: 3468676|\n|\n|" ], [ $addressAttributes, @@ -112,7 +112,7 @@ United States<br/>\nT: <a href=\"tel:3468676\">3468676</a>\n\n", [ $address, AttributeDataFactory::OUTPUT_FORMAT_PDF, - "John Smith|\n\nGreen str, 67\n\n\n\n\nCityM,|\nAlabama, 75477| + "John Smith|\n\nGreen str, 67|\n\n\n\nCityM, Alabama, 75477| United States|\nT: 3468676|\n|\n|" ], [ diff --git a/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommandTest.php b/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommandTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1b0fe2de866e817ab78e646087b9ebe248a60818 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommandTest.php @@ -0,0 +1,291 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Console\Command\App; + +use Magento\Deploy\Console\Command\App\SensitiveConfigSet\CollectorFactory; +use Magento\Deploy\Console\Command\App\SensitiveConfigSet\InteractiveCollector; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\DeploymentConfig\Reader; +use Magento\Framework\App\DeploymentConfig\Writer; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Config\File\ConfigFilePool; +use Magento\Framework\Filesystem; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class SensitiveConfigSetCommandTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Reader + */ + private $reader; + + /** + * @var ConfigFilePool + */ + private $configFilePool; + + /** + * @var array + */ + private $config; + + /** + * @var Filesystem + */ + private $filesystem; + + public function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->reader = $this->objectManager->get(Reader::class); + $this->configFilePool = $this->objectManager->get(ConfigFilePool::class); + $this->config = $this->loadConfig(); + $this->filesystem = $this->objectManager->get(Filesystem::class); + $this->filesystem->getDirectoryWrite(DirectoryList::CONFIG)->writeFile( + $this->getFileName(), + file_get_contents(__DIR__ . '/../../../_files/_config.local.php') + ); + } + + /** + * @param $scope + * @param $scopeCode + * @param callable $assertCallback + * @magentoDataFixture Magento/Store/_files/website.php + * @magentoDbIsolation enabled + * @dataProvider testExecuteDataProvider + */ + public function testExecute($scope, $scopeCode, callable $assertCallback) + { + $outputMock = $this->getMock(OutputInterface::class); + $outputMock->expects($this->at(0)) + ->method('writeln') + ->with('<info>Configuration value saved in app/etc/config.php</info>'); + + $inputMock = $this->getMock(InputInterface::class); + $inputMock->expects($this->exactly(2)) + ->method('getArgument') + ->withConsecutive( + [SensitiveConfigSetCommand::INPUT_ARGUMENT_PATH], + [SensitiveConfigSetCommand::INPUT_ARGUMENT_VALUE] + ) + ->willReturnOnConsecutiveCalls( + 'some/config/path_two', + 'sensitiveValue' + ); + $inputMock->expects($this->exactly(3)) + ->method('getOption') + ->withConsecutive( + [SensitiveConfigSetCommand::INPUT_OPTION_SCOPE], + [SensitiveConfigSetCommand::INPUT_OPTION_SCOPE_CODE], + [SensitiveConfigSetCommand::INPUT_OPTION_INTERACTIVE] + ) + ->willReturnOnConsecutiveCalls( + $scope, + $scopeCode, + null + ); + + /** @var SensitiveConfigSetCommand command */ + $command = $this->objectManager->create(SensitiveConfigSetCommand::class); + $command->run($inputMock, $outputMock); + + $config = $this->loadConfig(); + + $assertCallback($config); + } + + public function testExecuteDataProvider() + { + return [ + [ + ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + null, + function (array $config) { + $this->assertTrue(isset($config['system']['default']['some']['config']['path_two'])); + $this->assertEquals( + 'sensitiveValue', + $config['system']['default']['some']['config']['path_two'] + ); + } + ], + [ + 'website', + 'test', + function (array $config) { + $this->assertTrue(isset($config['system']['website']['test']['some']['config']['path_two'])); + $this->assertEquals( + 'sensitiveValue', + $config['system']['website']['test']['some']['config']['path_two'] + ); + } + ] + ]; + } + + /** + * @param $scope + * @param $scopeCode + * @param callable $assertCallback + * @magentoDataFixture Magento/Store/_files/website.php + * @magentoDbIsolation enabled + * @dataProvider testExecuteInteractiveDataProvider + */ + public function testExecuteInteractive($scope, $scopeCode, callable $assertCallback) + { + $inputMock = $this->getMock(InputInterface::class); + $outputMock = $this->getMock(OutputInterface::class); + $outputMock->expects($this->at(0)) + ->method('writeln') + ->with('<info>Please set configuration values or skip them by pressing [Enter]:</info>'); + $outputMock->expects($this->at(1)) + ->method('writeln') + ->with('<info>Configuration values saved in app/etc/config.php</info>'); + $inputMock->expects($this->exactly(3)) + ->method('getOption') + ->withConsecutive( + [SensitiveConfigSetCommand::INPUT_OPTION_SCOPE], + [SensitiveConfigSetCommand::INPUT_OPTION_SCOPE_CODE], + [SensitiveConfigSetCommand::INPUT_OPTION_INTERACTIVE] + ) + ->willReturnOnConsecutiveCalls( + $scope, + $scopeCode, + true + ); + + $questionHelperMock = $this->getMock(QuestionHelper::class); + $questionHelperMock->expects($this->exactly(3)) + ->method('ask') + ->willReturn('sensitiveValue'); + + $interactiveCollectorMock = $this->objectManager->create( + InteractiveCollector::class, + [ + 'questionHelper' => $questionHelperMock + ] + ); + $collectorFactoryMock = $this->getMockBuilder(CollectorFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $collectorFactoryMock->expects($this->once()) + ->method('create') + ->with(CollectorFactory::TYPE_INTERACTIVE) + ->willReturn($interactiveCollectorMock); + + /** @var SensitiveConfigSetCommand command */ + $command = $this->objectManager->create( + SensitiveConfigSetCommand::class, + [ + 'collectorFactory' => $collectorFactoryMock + ] + ); + $command->run($inputMock, $outputMock); + + $config = $this->loadConfig(); + + $assertCallback($config); + } + + public function testExecuteInteractiveDataProvider() + { + return [ + [ + ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + null, + function (array $config) { + $this->assertTrue(isset($config['system']['default']['some']['config']['path_one'])); + $this->assertTrue(isset($config['system']['default']['some']['config']['path_two'])); + $this->assertTrue(isset($config['system']['default']['some']['config']['path_three'])); + $this->assertEquals( + 'sensitiveValue', + $config['system']['default']['some']['config']['path_one'] + ); + $this->assertEquals( + 'sensitiveValue', + $config['system']['default']['some']['config']['path_two'] + ); + $this->assertEquals( + 'sensitiveValue', + $config['system']['default']['some']['config']['path_three'] + ); + } + ], + [ + 'website', + 'test', + function (array $config) { + $this->assertTrue(isset($config['system']['website']['test']['some']['config']['path_one'])); + $this->assertTrue(isset($config['system']['website']['test']['some']['config']['path_two'])); + $this->assertTrue(isset($config['system']['website']['test']['some']['config']['path_three'])); + $this->assertEquals( + 'sensitiveValue', + $config['system']['website']['test']['some']['config']['path_one'] + ); + $this->assertEquals( + 'sensitiveValue', + $config['system']['website']['test']['some']['config']['path_two'] + ); + $this->assertEquals( + 'sensitiveValue', + $config['system']['website']['test']['some']['config']['path_three'] + ); + } + ] + ]; + } + + public function tearDown() + { + $this->filesystem->getDirectoryWrite(DirectoryList::CONFIG)->delete( + $this->getFileName() + ); + $this->filesystem->getDirectoryWrite(DirectoryList::CONFIG)->writeFile( + $this->configFilePool->getPath(ConfigFilePool::APP_CONFIG), + "<?php\n return array();\n" + ); + /** @var Writer $writer */ + $writer = $this->objectManager->get(Writer::class); + $writer->saveConfig([ConfigFilePool::APP_CONFIG => $this->config]); + } + + /** + * @return string + */ + private function getFileName() + { + /** @var ConfigFilePool $configFilePool */ + $configFilePool = $this->objectManager->get(ConfigFilePool::class); + $filePool = $configFilePool->getInitialFilePools(); + + return $filePool[ConfigFilePool::LOCAL][ConfigFilePool::APP_CONFIG]; + } + + /** + * @return array + */ + private function loadConfig() + { + return $this->reader->loadConfigFile( + ConfigFilePool::APP_CONFIG, + $this->configFilePool->getPath(ConfigFilePool::APP_CONFIG), + true + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Deploy/_files/_config.local.php b/dev/tests/integration/testsuite/Magento/Deploy/_files/_config.local.php new file mode 100644 index 0000000000000000000000000000000000000000..ae4630b1a2b07e021ba1e7e1e409f4e1e81a99a6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Deploy/_files/_config.local.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +return [ + 'scopes' => [ + 'websites' => [] + ], + /** + * The configuration file doesn't contain sensitive data for security reasons. + * Sensitive data can be stored in the following environment variables: + * CONFIG__DEFAULT__SOME__CONFIG__PATH_ONE for some/config/path_one + * CONFIG__DEFAULT__SOME__CONFIG__PATH_TWO for some/config/path_two + * CONFIG__DEFAULT__SOME__CONFIG__PATH_THREE for some/config/path_three + */ + 'system' => [ + 'default' => [ + 'web' => [], + 'general' => [] + ] + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Block/Catalog/Edit/FormTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Block/Catalog/Edit/FormTest.php index 23c8c100b63ef2b4f065b848693f5b6d4ac76d7c..159dc80197834af1de6635eefda9565f551760e3 100644 --- a/dev/tests/integration/testsuite/Magento/UrlRewrite/Block/Catalog/Edit/FormTest.php +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Block/Catalog/Edit/FormTest.php @@ -96,6 +96,7 @@ class FormTest extends \PHPUnit_Framework_TestCase */ public function testGetEntityStores($productData, $categoryData, $expectedStores) { + $this->markTestSkipped('Skipped until MAGETWO-63018'); $args = []; if ($productData) { $args['product'] = $this->objectManager->create( @@ -121,6 +122,7 @@ class FormTest extends \PHPUnit_Framework_TestCase */ public function testGetEntityStoresProductStoresException() { + $this->markTestSkipped('Skipped until MAGETWO-63018'); $args = [ 'product' => $this->objectManager->create( \Magento\Catalog\Model\Product::class, diff --git a/dev/tests/static/framework/autoload.php b/dev/tests/static/framework/autoload.php index a5750d9303c945e6f831c23b88d919b96d50cd44..fc3b62fc97287d9be6d6f72db106436c4ab7f84f 100644 --- a/dev/tests/static/framework/autoload.php +++ b/dev/tests/static/framework/autoload.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +use \Magento\Framework\App\Filesystem\DirectoryList; + $baseDir = realpath(__DIR__ . '/../../../../'); require $baseDir . '/app/autoload.php'; $testsBaseDir = $baseDir . '/dev/tests/static'; @@ -16,4 +18,6 @@ $autoloadWrapper->addPsr4( $testsBaseDir . '/../integration/framework/Magento/TestFramework/', ] ); -$autoloadWrapper->addPsr4('Magento\\', $baseDir . '/var/generation/Magento/'); + +$generatedCode = DirectoryList::getDefaultConfig()[DirectoryList::GENERATION][DirectoryList::PATH]; +$autoloadWrapper->addPsr4('Magento\\', $baseDir . '/' . $generatedCode . '/Magento/'); 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 3afe3af79b14ff3177a0dc1c5ea9fe42e93b6331..ebe8501337efff410bba83f4f291b3433247a893 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 @@ -109,7 +109,7 @@ Magento/SalesRule/Model/Resource/Report/Rule Magento/SalesRule/Model/Resource/Rule Magento/Theme/Block/Adminhtml/System/Design/Theme/Edit Magento/User/Block/User/Edit -var/generation +generated/code Magento/Newsletter/Block/Adminhtml/Template/Grid/Renderer Magento/Newsletter/Model/Template/Filter Magento/Newsletter/Model/Resource/Subscriber diff --git a/dev/tests/unit/framework/autoload.php b/dev/tests/unit/framework/autoload.php index 4b2adbe17673fb7f33a60f7183ffd190833d2904..b4987145872d44c854c993fad60cb6ce02422bf0 100644 --- a/dev/tests/unit/framework/autoload.php +++ b/dev/tests/unit/framework/autoload.php @@ -4,10 +4,12 @@ * See COPYING.txt for license details. */ +use Magento\Framework\App\Filesystem\DirectoryList; + $autoloader = new \Magento\Framework\TestFramework\Unit\Autoloader\ExtensionGeneratorAutoloader( new \Magento\Framework\Code\Generator\Io( new \Magento\Framework\Filesystem\Driver\File(), - TESTS_TEMP_DIR . '/var/generation' + TESTS_TEMP_DIR . '/'. DirectoryList::getDefaultConfig()[DirectoryList::GENERATION][DirectoryList::PATH] ) ); spl_autoload_register([$autoloader, 'load']); diff --git a/generated/.htaccess b/generated/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..896fbc5a341ea313ff3abd503808842757678c1d --- /dev/null +++ b/generated/.htaccess @@ -0,0 +1,2 @@ +Order deny,allow +Deny from all \ No newline at end of file diff --git a/lib/internal/Magento/Framework/App/Config/CommentParserInterface.php b/lib/internal/Magento/Framework/App/Config/CommentParserInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..efc917d31b0c70a1c0ee6a4b8a070f71fdbb322e --- /dev/null +++ b/lib/internal/Magento/Framework/App/Config/CommentParserInterface.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\App\Config; + +use Magento\Framework\Exception\FileSystemException; + +/** + * Interface CommentParserInterface + */ +interface CommentParserInterface +{ + /** + * Retrieve config list from file comments. + * + * @param string $fileName + * @return array + * @throws FileSystemException + */ + public function execute($fileName); +} diff --git a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php index ae8a9bd4dc64e66ecf8ab865f61541d9bdc94a6b..28fcbd3692c9e1dffb84b0a2ef98b1ffb0633d96 100644 --- a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php +++ b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php @@ -25,7 +25,10 @@ class PhpFormatter implements FormatterInterface foreach ($data as $key => $value) { $comment = ' '; if (!empty($comments[$key])) { - $comment = " /**\n * " . str_replace("\n", "\n * ", var_export($comments[$key], true)) . "\n */\n"; + $exportedComment = is_string($comments[$key]) + ? $comments[$key] + : var_export($comments[$key], true); + $comment = " /**\n * " . str_replace("\n", "\n * ", $exportedComment) . "\n */\n"; } $space = is_array($value) ? " \n" : ' '; $elements[] = $comment . var_export($key, true) . ' =>' . $space . var_export($value, true); diff --git a/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php b/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php index ae1a6544ef2cbf05ef8bd4de84188472113ffa9c..57c903b49198cb5b4c99c8b6dcb2fcd8bcb3ed94 100644 --- a/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php +++ b/lib/internal/Magento/Framework/App/Filesystem/DirectoryList.php @@ -5,6 +5,8 @@ */ namespace Magento\Framework\App\Filesystem; +use Magento\Framework\Code\Generator\Io; + /** * A Magento application specific list of directories */ @@ -120,8 +122,8 @@ class DirectoryList extends \Magento\Framework\Filesystem\DirectoryList self::VAR_DIR => [parent::PATH => 'var'], self::CACHE => [parent::PATH => 'var/cache'], self::LOG => [parent::PATH => 'var/log'], - self::DI => [parent::PATH => 'var/di'], - self::GENERATION => [parent::PATH => 'var/generation'], + self::DI => [parent::PATH => 'generated/metadata'], + self::GENERATION => [parent::PATH => Io::DEFAULT_DIRECTORY], self::SESSION => [parent::PATH => 'var/session'], self::MEDIA => [parent::PATH => 'pub/media', parent::URL_PATH => 'pub/media'], self::STATIC_VIEW => [parent::PATH => 'pub/static', parent::URL_PATH => 'pub/static'], diff --git a/lib/internal/Magento/Framework/App/Scope/ValidatorInterface.php b/lib/internal/Magento/Framework/App/Scope/ValidatorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8063d61d8f5b29395183b9742dc45d6084957955 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Scope/ValidatorInterface.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\App\Scope; + +use Magento\Framework\Exception\LocalizedException; + +interface ValidatorInterface +{ + /** + * Validate if exists given scope and scope code + * otherwise, throws an exception with appropriate message. + * + * @param string $scope + * @param string $scopeCode + * @return boolean + * @throws LocalizedException + */ + public function isValid($scope, $scopeCode = null); +} diff --git a/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php b/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php index 0f86b778a04d2d1c52535adf4f6eafb7d5af2795..9f6894c0a68350e86b9e96a8111de123ac11d574 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php @@ -3,7 +3,6 @@ * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Framework\App\Test\Unit\DeploymentConfig\Writer; use \Magento\Framework\App\DeploymentConfig\Writer\PhpFormatter; @@ -48,9 +47,9 @@ class PhpFormatterTest extends \PHPUnit_Framework_TestCase ]; $comments1 = ['ns2' => 'comment for namespace 2']; $comments2 = [ - 'ns1' => 'comment for namespace 1', - 'ns2' => "comment for namespace 2.\nNext comment for namespace 2", - 'ns3' => 'comment for namespace 3', + 'ns1' => 'comment for\' namespace 1', + 'ns2' => "comment for namespace 2.\nNext comment for' namespace 2", + 'ns3' => 'comment for" namespace 3', 'ns4' => 'comment for namespace 4', 'ns5' => 'comment for unexisted namespace 5', ]; @@ -71,7 +70,7 @@ return array ( ), ), /** - * 'comment for namespace 2' + * comment for namespace 2 */ 'ns2' => array ( @@ -89,7 +88,7 @@ TEXT; <?php return array ( /** - * 'comment for namespace 1' + * comment for' namespace 1 */ 'ns1' => array ( @@ -105,8 +104,8 @@ return array ( ), ), /** - * 'comment for namespace 2. - * Next comment for namespace 2' + * comment for namespace 2. + * Next comment for' namespace 2 */ 'ns2' => array ( @@ -116,11 +115,11 @@ return array ( ), ), /** - * 'comment for namespace 3' + * comment for" namespace 3 */ 'ns3' => 'just text', /** - * 'comment for namespace 4' + * comment for namespace 4 */ 'ns4' => 'just text' ); diff --git a/lib/internal/Magento/Framework/Code/GeneratedFiles.php b/lib/internal/Magento/Framework/Code/GeneratedFiles.php index e33b311e74c629735d60485874c42769ad12a11e..9656ef6c4b9fe794b4ca6f915027905f106c1e3d 100644 --- a/lib/internal/Magento/Framework/Code/GeneratedFiles.php +++ b/lib/internal/Magento/Framework/Code/GeneratedFiles.php @@ -57,7 +57,7 @@ class GeneratedFiles } /** - * Clean var/generation, var/di and var/cache + * Clean generated/code, generated/metadata and var/cache * * @return void */ @@ -81,12 +81,12 @@ class GeneratedFiles $generationPath = $this->write->getRelativePath($this->directoryList->getPath(DirectoryList::GENERATION)); $diPath = $this->write->getRelativePath($this->directoryList->getPath(DirectoryList::DI)); - // Clean var/generation dir + // Clean generated/code dir if ($this->write->isDirectory($generationPath)) { $this->write->delete($generationPath); } - // Clean var/di + // Clean generated/metadata if ($this->write->isDirectory($diPath)) { $this->write->delete($diPath); } @@ -101,7 +101,7 @@ class GeneratedFiles } /** - * Create flag for cleaning up var/generation, var/di and var/cache directories for subsequent + * Create flag for cleaning up generated/code, generated/metadata and var/cache directories for subsequent * regeneration of this content * * @return void diff --git a/lib/internal/Magento/Framework/Code/Generator/Io.php b/lib/internal/Magento/Framework/Code/Generator/Io.php index 05ddb46e948325eaba29f91a570ddb8412948e40..85449fe61749fa6dcdbb941a5b28c436cf663e90 100644 --- a/lib/internal/Magento/Framework/Code/Generator/Io.php +++ b/lib/internal/Magento/Framework/Code/Generator/Io.php @@ -14,7 +14,7 @@ class Io * Default code generation directory * Should correspond the value from \Magento\Framework\Filesystem */ - const DEFAULT_DIRECTORY = 'var/generation'; + const DEFAULT_DIRECTORY = 'generated/code'; /** * Path to directory where new file must be created diff --git a/lib/internal/Magento/Framework/Console/Cli.php b/lib/internal/Magento/Framework/Console/Cli.php index 5a89d0f6b60571680068463df5b78f85951c809c..855e792ec21b9923ce9020fde7ac3eb41040d0bb 100644 --- a/lib/internal/Magento/Framework/Console/Cli.php +++ b/lib/internal/Magento/Framework/Console/Cli.php @@ -5,102 +5,106 @@ */ namespace Magento\Framework\Console; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\State; use Magento\Framework\Composer\ComposerJsonFinder; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Console\Application as SymfonyApplication; +use Magento\Framework\Exception\FileSystemException; +use Magento\Setup\Model\ObjectManagerProvider; +use Symfony\Component\Console; use Magento\Framework\App\Bootstrap; use Magento\Framework\Filesystem\Driver\File; use Magento\Framework\Shell\ComplexParameter; use Magento\Setup\Console\CompilerPreparation; -use \Magento\Framework\App\ProductMetadata; +use Magento\Framework\App\ProductMetadata; +use Magento\Framework\ObjectManagerInterface; +use Zend\ServiceManager\ServiceManager; /** - * Magento 2 CLI Application. This is the hood for all command line tools supported by Magento + * Magento 2 CLI Application. + * This is the hood for all command line tools supported by Magento. * * {@inheritdoc} * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Cli extends SymfonyApplication +class Cli extends Console\Application { /** - * Name of input option + * Name of input option. */ const INPUT_KEY_BOOTSTRAP = 'bootstrap'; - /** - * Cli exit codes + /**#@+ + * Cli exit codes. */ const RETURN_SUCCESS = 0; const RETURN_FAILURE = 1; + /**#@-*/ - /** @var \Zend\ServiceManager\ServiceManager */ + /** + * Service Manager. + * + * @var ServiceManager + */ private $serviceManager; /** - * Initialization exception + * Initialization exception. * * @var \Exception */ private $initException; /** - * @param string $name application name - * @param string $version application version - * @SuppressWarnings(PHPMD.ExitExpression) + * Object Manager. + * + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @param string $name the application name + * @param string $version the application version */ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') { $this->serviceManager = \Zend\Mvc\Application::init(require BP . '/setup/config/application.config.php') ->getServiceManager(); - $generationDirectoryAccess = new GenerationDirectoryAccess($this->serviceManager); - if (!$generationDirectoryAccess->check()) { - $output = new ConsoleOutput(); - $output->writeln( - '<error>Command line user does not have read and write permissions on var/generation directory. Please' - . ' address this issue before using Magento command line.</error>' - ); - exit(0); - } - /** - * Temporary workaround until the compiler is able to clear the generation directory - * @todo remove after MAGETWO-44493 resolved - */ - if (class_exists(CompilerPreparation::class)) { - $compilerPreparation = new CompilerPreparation($this->serviceManager, new ArgvInput(), new File()); - $compilerPreparation->handleCompilerEnvironment(); - } + + $this->assertCompilerPreparation(); + $this->initObjectManager(); + $this->assertGenerationPermissions(); if ($version == 'UNKNOWN') { - $directoryList = new DirectoryList(BP); + $directoryList = new DirectoryList(BP); $composerJsonFinder = new ComposerJsonFinder($directoryList); - $productMetadata = new ProductMetadata($composerJsonFinder); + $productMetadata = new ProductMetadata($composerJsonFinder); $version = $productMetadata->getVersion(); } + parent::__construct($name, $version); } /** - * Process an error happened during initialization of commands, if any + * {@inheritdoc} * - * @param InputInterface $input - * @param OutputInterface $output - * @return int - * @throws \Exception + * @throws \Exception the exception in case of unexpected error */ - public function doRun(InputInterface $input, OutputInterface $output) + public function doRun(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $exitCode = parent::doRun($input, $output); + if ($this->initException) { $output->writeln( "<error>We're sorry, an error occurred. Try clearing the cache and code generation directories. " - . "By default, they are: var/cache, var/di, var/generation, and var/page_cache.</error>" + . "By default, they are: " . $this->getDefaultDirectoryPath(DirectoryList::CACHE) . ", " + . $this->getDefaultDirectoryPath(DirectoryList::DI) . ", " + . $this->getDefaultDirectoryPath(DirectoryList::GENERATION) . ", and var/page_cache.</error>" ); + throw $this->initException; } + return $exitCode; } @@ -113,46 +117,142 @@ class Cli extends SymfonyApplication } /** - * Gets application commands + * Gets application commands. * - * @return array + * @return array a list of available application commands */ protected function getApplicationCommands() { $commands = []; try { - $bootstrapParam = new ComplexParameter(self::INPUT_KEY_BOOTSTRAP); - $params = $bootstrapParam->mergeFromArgv($_SERVER, $_SERVER); - $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null; - $bootstrap = Bootstrap::create(BP, $params); - $objectManager = $bootstrap->getObjectManager(); - /** @var \Magento\Setup\Model\ObjectManagerProvider $omProvider */ - $omProvider = $this->serviceManager->get(\Magento\Setup\Model\ObjectManagerProvider::class); - $omProvider->setObjectManager($objectManager); - if (class_exists(\Magento\Setup\Console\CommandList::class)) { $setupCommandList = new \Magento\Setup\Console\CommandList($this->serviceManager); $commands = array_merge($commands, $setupCommandList->getCommands()); } - if ($objectManager->get(\Magento\Framework\App\DeploymentConfig::class)->isAvailable()) { - /** @var \Magento\Framework\Console\CommandListInterface */ - $commandList = $objectManager->create(\Magento\Framework\Console\CommandListInterface::class); + if ($this->objectManager->get(DeploymentConfig::class)->isAvailable()) { + /** @var CommandListInterface */ + $commandList = $this->objectManager->create(CommandListInterface::class); $commands = array_merge($commands, $commandList->getCommands()); } - $commands = array_merge($commands, $this->getVendorCommands($objectManager)); + $commands = array_merge( + $commands, + $this->getVendorCommands($this->objectManager) + ); } catch (\Exception $e) { $this->initException = $e; } + return $commands; } /** - * Gets vendor commands + * Object Manager initialization. * - * @param \Magento\Framework\ObjectManagerInterface $objectManager - * @return array + * @return void + * @SuppressWarnings(PHPMD.ExitExpression) + */ + private function initObjectManager() + { + try { + $params = (new ComplexParameter(self::INPUT_KEY_BOOTSTRAP))->mergeFromArgv($_SERVER, $_SERVER); + $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null; + + $this->objectManager = Bootstrap::create(BP, $params)->getObjectManager(); + + /** @var ObjectManagerProvider $omProvider */ + $omProvider = $this->serviceManager->get(ObjectManagerProvider::class); + $omProvider->setObjectManager($this->objectManager); + } catch (FileSystemException $exception) { + $this->writeGenerationDirectoryReadError(); + + exit(static::RETURN_FAILURE); + } + } + + /** + * Checks whether generation directory is read-only. + * Depends on the current mode: + * production - application will proceed + * default - application will be terminated + * developer - application will be terminated + * + * @return void + * @SuppressWarnings(PHPMD.ExitExpression) + */ + private function assertGenerationPermissions() + { + /** @var GenerationDirectoryAccess $generationDirectoryAccess */ + $generationDirectoryAccess = $this->objectManager->create( + GenerationDirectoryAccess::class, + ['serviceManager' => $this->serviceManager] + ); + /** @var State $state */ + $state = $this->objectManager->get(State::class); + + if ( + $state->getMode() !== State::MODE_PRODUCTION + && !$generationDirectoryAccess->check() + ) { + $this->writeGenerationDirectoryReadError(); + + exit(static::RETURN_FAILURE); + } + } + + /** + * Checks whether compiler is being prepared. + * + * @return void + * @SuppressWarnings(PHPMD.ExitExpression) + */ + private function assertCompilerPreparation() + { + /** + * Temporary workaround until the compiler is able to clear the generation directory + * @todo remove after MAGETWO-44493 resolved + */ + if (class_exists(CompilerPreparation::class)) { + $compilerPreparation = new CompilerPreparation( + $this->serviceManager, + new Console\Input\ArgvInput(), + new File() + ); + + try { + $compilerPreparation->handleCompilerEnvironment(); + } catch (FileSystemException $e) { + $this->writeGenerationDirectoryReadError(); + + exit(static::RETURN_FAILURE); + } + } + } + + /** + * Writes read error to console. + * + * @return void + */ + private function writeGenerationDirectoryReadError() + { + $output = new \Symfony\Component\Console\Output\ConsoleOutput(); + $output->writeln( + '<error>' + . 'Command line user does not have read and write permissions on ' + . $this->getDefaultDirectoryPath(DirectoryList::GENERATION) . ' directory. ' + . 'Please address this issue before using Magento command line.' + . '</error>' + ); + } + + /** + * Retrieves vendor commands. + * + * @param ObjectManagerInterface $objectManager the object manager + * + * @return array an array with external commands */ protected function getVendorCommands($objectManager) { @@ -165,6 +265,25 @@ class Cli extends SymfonyApplication ); } } + return $commands; } + + /** + * Get default directory path by code + * + * @param string $code + * @return string + */ + private function getDefaultDirectoryPath($code) + { + $config = DirectoryList::getDefaultConfig(); + $result = ''; + + if (isset($config[$code][DirectoryList::PATH])) { + $result = $config[$code][DirectoryList::PATH]; + } + + return $result; + } } diff --git a/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php b/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php index b0baf410d669f16eab5899e07b9e6103a274bd82..a2a1fc9be20e0a4702734cb7065011aab63ab564 100644 --- a/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php +++ b/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php @@ -14,7 +14,7 @@ use Zend\ServiceManager\ServiceManager; use Magento\Setup\Mvc\Bootstrap\InitParamListener; /** - * Check var/generation read and write access + * Check generated/code read and write access */ class GenerationDirectoryAccess { @@ -33,7 +33,7 @@ class GenerationDirectoryAccess } /** - * Check var/generation read and write access + * Check generated/code read and write access * * @return bool */ diff --git a/lib/internal/Magento/Framework/DB/TemporaryTableService.php b/lib/internal/Magento/Framework/DB/TemporaryTableService.php new file mode 100644 index 0000000000000000000000000000000000000000..36a96f69ad67c6d46527121c377a2b2500ea309c --- /dev/null +++ b/lib/internal/Magento/Framework/DB/TemporaryTableService.php @@ -0,0 +1,164 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\DB; + +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Select; + +/** + * Class TemporaryTableService creates a temporary table in mysql from a Magento\Framework\DB\Select. + * Use this class to create an index with that that you want to query later for quick data access + * + * @api + */ +class TemporaryTableService +{ + const INDEX_METHOD_HASH = 'HASH'; + const DB_ENGINE_INNODB = 'INNODB'; + + /** + * @var string[] + */ + private $allowedIndexMethods; + + /** + * @var string[] + */ + private $allowedEngines; + + /** + * @var \Magento\Framework\Math\Random + */ + private $random; + + /** + * @var AdapterInterface[] + */ + private $createdTableAdapters = []; + + /** + * @param \Magento\Framework\Math\Random $random + * @param string[] $allowedIndexMethods + * @param string[] $allowedEngines + */ + public function __construct( + \Magento\Framework\Math\Random $random, + $allowedIndexMethods = [], + $allowedEngines = [] + ) { + $this->random = $random; + $this->allowedIndexMethods = $allowedIndexMethods; + $this->allowedEngines = $allowedEngines; + } + + /** + * Creates a temporary table from select removing duplicate rows if you have a union in your select + * This method should always be paired with dropTable to ensure cleanup + * Make sure you index your data so you can query it fast + * You can choose from memory or file table and provide indexes to ensure fast data query + * + * Example: createFromSelect( + * $selectObject, + * $this->resourceConnection->getConnection(), + * [ + * 'PRIMARY' => ['primary_id'], + * 'some_single_field_index' => ['field'], + * 'UNQ_some_multiple_field_index' => ['field1', 'field2'], + * ] + * ) + * Note that indexes names with UNQ_ prefix, will be created as unique + * + * @param Select $select + * @param AdapterInterface $adapter + * @param array $indexes + * @param string $indexMethod + * @param string $dbEngine + * @return string + * @throws \InvalidArgumentException + */ + public function createFromSelect( + Select $select, + AdapterInterface $adapter, + array $indexes = [], + $indexMethod = self::INDEX_METHOD_HASH, + $dbEngine = self::DB_ENGINE_INNODB + ) { + if (!in_array($indexMethod, $this->allowedIndexMethods)) { + throw new \InvalidArgumentException( + sprintf('indexMethod must be one of %s', implode(',', $this->allowedIndexMethods)) + ); + } + + if (!in_array($dbEngine, $this->allowedEngines)) { + throw new \InvalidArgumentException( + sprintf('dbEngine must be one of %s', implode(',', $this->allowedEngines)) + ); + } + + $name = $this->random->getUniqueHash('tmp_select_'); + + $indexStatements = []; + foreach ($indexes as $indexName => $columns) { + $renderedColumns = implode(',', array_map([$adapter, 'quoteIdentifier'], $columns)); + + $indexType = sprintf( + 'INDEX %s USING %s', + $adapter->quoteIdentifier($indexName), + $indexMethod + ); + + if ($indexName === 'PRIMARY') { + $indexType = 'PRIMARY KEY'; + } elseif (strpos($indexName, 'UNQ_') === 0) { + $indexType = sprintf('UNIQUE %s', $adapter->quoteIdentifier($indexName)); + } + + $indexStatements[] = sprintf('%s(%s)', $indexType, $renderedColumns); + } + + $statement = sprintf( + 'CREATE TEMPORARY TABLE %s %s ENGINE=%s IGNORE (%s)', + $adapter->quoteIdentifier($name), + $indexStatements ? '(' . implode(',', $indexStatements) . ')' : '', + $adapter->quoteIdentifier($dbEngine), + "{$select}" + ); + + $adapter->query( + $statement, + $select->getBind() + ); + + $this->createdTableAdapters[$name] = $adapter; + + return $name; + } + + /** + * Method used to drop a table by name + * This class will hold all temporary table names in createdTableAdapters array + * so we can dispose them once we're finished + * + * Example: dropTable($name) + * where $name is a variable that holds the name for a previously created temporary table + * by using "createFromSelect" method + * + * @param string $name + * @return bool + */ + public function dropTable($name) + { + if (!empty($this->createdTableAdapters)) { + if (isset($this->createdTableAdapters[$name]) && !empty($name)) { + $adapter = $this->createdTableAdapters[$name]; + $adapter->dropTemporaryTable($name); + unset($this->createdTableAdapters[$name]); + return true; + } + } + return false; + } +} diff --git a/lib/internal/Magento/Framework/DB/Test/Unit/TemporaryTableServiceTest.php b/lib/internal/Magento/Framework/DB/Test/Unit/TemporaryTableServiceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..620a197386fde6bcfa923fff03a3919511ce3cb5 --- /dev/null +++ b/lib/internal/Magento/Framework/DB/Test/Unit/TemporaryTableServiceTest.php @@ -0,0 +1,209 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\DB\Test\Unit; + +use Magento\Framework\DB\TemporaryTableService; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Select; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Math\Random; + +class TemporaryTableServiceTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var TemporaryTableService|\PHPUnit_Framework_MockObject_MockObject + */ + private $temporaryTableService; + + /** + * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $adapterMock; + + /** + * @var Random|\PHPUnit_Framework_MockObject_MockObject + */ + private $randomMock; + + /** + * @var Select|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectMock; + + /** + * Set up + * + * @return void + */ + protected function setUp() + { + $this->adapterMock = $this->getMock(AdapterInterface::class); + $this->selectMock = $this->getMock(Select::class, [], [], '', false); + $this->randomMock = $this->getMock(Random::class, [], [], '', false); + $this->temporaryTableService = (new ObjectManager($this))->getObject( + TemporaryTableService::class, + [ + 'random' => $this->randomMock, + 'allowedIndexMethods' => ['HASH'], + 'allowedEngines' => ['INNODB'] + ] + ); + } + + /** + * Run test createFromSelect method + * + * @return void + */ + public function testCreateFromSelectWithException() + { + $this->setExpectedException(\InvalidArgumentException::class); + $random = 'random_table'; + $indexes = [ + ['PRIMARY' => ['primary_column_name']], + 'CREATE TEMPORARY TABLE random_table (PRIMARY KEY(primary_column_name)) ENGINE=INNODB IGNORE ' + . '(select * from sometable)' + ]; + + $this->assertEquals( + $random, + $this->temporaryTableService->createFromSelect( + $this->selectMock, + $this->adapterMock, + $indexes, + TemporaryTableService::INDEX_METHOD_HASH . "Other", + TemporaryTableService::DB_ENGINE_INNODB . "Other" + ) + ); + } + + /** + * Run test createFromSelect method + * + * @param array $indexes + * @param string $expectedSelect + * @dataProvider createFromSelectDataProvider + * @return void + */ + public function testCreateFromSelect($indexes, $expectedSelect) + { + $selectString = 'select * from sometable'; + $random = 'random_table'; + + $this->randomMock->expects($this->once()) + ->method('getUniqueHash') + ->willReturn($random); + + $this->adapterMock->expects($this->once()) + ->method('query') + ->with($expectedSelect) + ->willReturnSelf(); + + $this->adapterMock->expects($this->once()) + ->method('query') + ->willReturnSelf(); + + $this->adapterMock->expects($this->any()) + ->method('quoteIdentifier') + ->willReturnArgument(0); + + $this->selectMock->expects($this->once()) + ->method('getBind') + ->willReturn(['bind']); + + $this->selectMock->expects($this->any()) + ->method('__toString') + ->willReturn($selectString); + + $this->assertEquals( + $random, + $this->temporaryTableService->createFromSelect( + $this->selectMock, + $this->adapterMock, + $indexes + ) + ); + } + + /** + * Run test dropTable method when createdTables array of TemporaryTableService is empty + * + * @return void + */ + public function testDropTableWhenCreatedTablesArrayIsEmpty() + { + $this->assertFalse($this->temporaryTableService->dropTable('tmp_select_table')); + } + + /** + * Run test dropTable method when data exists in createdTables array of TemporaryTableService + * + * @param string $tableName + * @param bool $assertion + * + * @dataProvider dropTableWhenCreatedTablesArrayNotEmptyDataProvider + * @return void + */ + public function testDropTableWhenCreatedTablesArrayNotEmpty($tableName, $assertion) + { + $createdTableAdapters = new \ReflectionProperty($this->temporaryTableService, 'createdTableAdapters'); + $createdTableAdapters->setAccessible(true); + $createdTableAdapters->setValue($this->temporaryTableService, ['tmp_select_table' => $this->adapterMock]); + $createdTableAdapters->setAccessible(false); + + $this->adapterMock->expects($this->any()) + ->method('dropTemporaryTable') + ->willReturn(true); + + $this->assertEquals($this->temporaryTableService->dropTable($tableName), $assertion); + } + + /** + * @return array + */ + public function createFromSelectDataProvider() + { + return [ + [ + ['PRIMARY' => ['primary_column_name']], + 'CREATE TEMPORARY TABLE random_table (PRIMARY KEY(primary_column_name)) ENGINE=INNODB IGNORE ' + . '(select * from sometable)' + ], + [ + ['UNQ_INDX' => ['column1', 'column2']], + 'CREATE TEMPORARY TABLE random_table (UNIQUE UNQ_INDX(column1,column2)) ENGINE=INNODB IGNORE ' + . '(select * from sometable)' + ], + [ + ['OTH_INDX' => ['column3', 'column4']], + 'CREATE TEMPORARY TABLE random_table (INDEX OTH_INDX USING HASH(column3,column4)) ENGINE=INNODB IGNORE ' + . '(select * from sometable)' + ], + [ + [ + 'PRIMARY' => ['primary_column_name'], + 'OTH_INDX' => ['column3', 'column4'], + 'UNQ_INDX' => ['column1', 'column2'] + ], + 'CREATE TEMPORARY TABLE random_table ' + . '(PRIMARY KEY(primary_column_name),' + . 'INDEX OTH_INDX USING HASH(column3,column4),UNIQUE UNQ_INDX(column1,column2)) ENGINE=INNODB IGNORE ' + . '(select * from sometable)' + ], + ]; + } + + /** + * @return array + */ + public function dropTableWhenCreatedTablesArrayNotEmptyDataProvider() + { + return [ + ['tmp_select_table_1', false], + ['tmp_select_table', true], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Module/Manager.php b/lib/internal/Magento/Framework/Module/Manager.php index 7df83917075651eb63f167aa759f9c931ba5edd7..cf22583184b1c89c79da06513a59c588a09faf25 100644 --- a/lib/internal/Magento/Framework/Module/Manager.php +++ b/lib/internal/Magento/Framework/Module/Manager.php @@ -3,90 +3,94 @@ * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +namespace Magento\Framework\Module; /** - * Module statuses manager + * Module status manager. + * + * Usage: + * + * $manager->isEnabled('Vendor_Module') */ -namespace Magento\Framework\Module; - class Manager { /** - * @var Output\ConfigInterface + * The checker of output modules. + * + * @var Output\ConfigInterface the config checker of output modules. + * @deprecated */ - private $_outputConfig; + private $outputConfig; /** - * @var ModuleListInterface + * The list of all modules. + * + * @var ModuleListInterface the list of all modules. */ - private $_moduleList; + private $moduleList; /** - * @var array + * The list of config paths to ignore. + * + * @var array the list of config paths to ignore. + * @deprecated */ - private $_outputConfigPaths; + private $outputConfigPaths; /** - * @param Output\ConfigInterface $outputConfig - * @param ModuleListInterface $moduleList - * @param array $outputConfigPaths + * Constructor. + * + * @param Output\ConfigInterface $outputConfig the checker of output modules + * @param ModuleListInterface $moduleList the list of all modules + * @param array $outputConfigPaths the list of config paths to ignore */ public function __construct( Output\ConfigInterface $outputConfig, ModuleListInterface $moduleList, array $outputConfigPaths = [] ) { - $this->_outputConfig = $outputConfig; - $this->_moduleList = $moduleList; - $this->_outputConfigPaths = $outputConfigPaths; + $this->outputConfig = $outputConfig; + $this->moduleList = $moduleList; + $this->outputConfigPaths = $outputConfigPaths; } /** - * Whether a module is enabled in the configuration or not + * Checks whether a module is enabled in the configuration or not. * - * @param string $moduleName Fully-qualified module name - * @return boolean + * @param string $moduleName the fully-qualified module name + * + * @return boolean true if module is enabled, false otherwise */ public function isEnabled($moduleName) { - return $this->_moduleList->has($moduleName); + return $this->moduleList->has($moduleName); } /** - * Whether a module output is permitted by the configuration or not + * Checks whether a module output is permitted by the configuration or not. + * + * @param string $moduleName the fully-qualified module name. * - * @param string $moduleName Fully-qualified module name * @return boolean + * @deprecated + * @see \Magento\Framework\Module\Manager::isEnabled() */ public function isOutputEnabled($moduleName) { - if (!$this->isEnabled($moduleName)) { - return false; - } - if (!$this->_isCustomOutputConfigEnabled($moduleName)) { - return false; - } - if ($this->_outputConfig->isEnabled($moduleName)) { - return false; - } - return true; + return $this->isEnabled($moduleName); } /** - * Whether a configuration switch for a module output permits output or not + * Checks whether a configuration switch for a module output permits output. * * @param string $moduleName Fully-qualified module name + * * @return boolean + * @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _isCustomOutputConfigEnabled($moduleName) { - if (isset($this->_outputConfigPaths[$moduleName])) { - $configPath = $this->_outputConfigPaths[$moduleName]; - if (defined($configPath)) { - $configPath = constant($configPath); - } - return $this->_outputConfig->isSetFlag($configPath); - } return true; } } diff --git a/lib/internal/Magento/Framework/Module/Output/Config.php b/lib/internal/Magento/Framework/Module/Output/Config.php index 27a99fdff4121c6514b51533d9e6626fb5f579f8..ca9d8d742d05cd410409f17161649471b17e08b1 100644 --- a/lib/internal/Magento/Framework/Module/Output/Config.php +++ b/lib/internal/Magento/Framework/Module/Output/Config.php @@ -7,6 +7,9 @@ */ namespace Magento\Framework\Module\Output; +/** + * @deprecated + */ class Config implements \Magento\Framework\Module\Output\ConfigInterface { /** @@ -41,7 +44,7 @@ class Config implements \Magento\Framework\Module\Output\ConfigInterface */ public function isEnabled($moduleName) { - return $this->isSetFlag(sprintf(self::XML_PATH_MODULE_OUTPUT_STATUS, $moduleName)); + return false; } /** @@ -49,6 +52,6 @@ class Config implements \Magento\Framework\Module\Output\ConfigInterface */ public function isSetFlag($path) { - return $this->_scopeConfig->isSetFlag($path, $this->_storeType); + return false; } } diff --git a/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php b/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php index e417391ed547455f1d6ccc2a14edbc7a02dc1ae2..2025f5c6f42c5b3312f0907b4f0deaaa3c6cefb9 100644 --- a/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php +++ b/lib/internal/Magento/Framework/Module/Output/ConfigInterface.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Module\Output; +/** + * @deprecated + */ interface ConfigInterface { /** diff --git a/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php b/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php index f8fdc438b940cd5dbf604056c26f6727d03c990e..284367c84a721e3ce0202e9ab1f180c0eba96473 100644 --- a/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php +++ b/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php @@ -5,106 +5,60 @@ */ namespace Magento\Framework\Module\Test\Unit; -use Magento\Framework\Module\Plugin\DbStatusValidator; +use Magento\Framework\Module\Manager; +use Magento\Framework\Module\ModuleListInterface; +use Magento\Framework\Module\Output\ConfigInterface; +use PHPUnit_Framework_MockObject_MockObject as Mock; class ManagerTest extends \PHPUnit_Framework_TestCase { /** * XPath in the configuration of a module output flag + * @deprecated */ const XML_PATH_OUTPUT_ENABLED = 'custom/is_module_output_enabled'; /** - * @var \Magento\Framework\Module\Manager + * @var Manager */ - private $_model; + private $model; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ModuleListInterface|Mock */ - private $_moduleList; + private $moduleList; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ConfigInterface|Mock + * @deprecated */ - private $_outputConfig; + private $outputConfig; protected function setUp() { - $this->_moduleList = $this->getMockForAbstractClass(\Magento\Framework\Module\ModuleListInterface::class); - $this->_moduleList->expects($this->any()) - ->method('getOne') - ->will($this->returnValueMap([ - ['Module_One', ['name' => 'One_Module', 'setup_version' => '1']], - ['Module_Two', ['name' => 'Two_Module', 'setup_version' => '2']], - ['Module_Three', ['name' => 'Two_Three']], - ])); - $this->_outputConfig = $this->getMockForAbstractClass(\Magento\Framework\Module\Output\ConfigInterface::class); - $this->_model = new \Magento\Framework\Module\Manager( - $this->_outputConfig, - $this->_moduleList, - [ - 'Module_Two' => self::XML_PATH_OUTPUT_ENABLED, - ] + $this->moduleList = $this->getMockBuilder(ModuleListInterface::class) + ->getMockForAbstractClass(); + $this->outputConfig = $this->getMockBuilder(ConfigInterface::class) + ->getMockForAbstractClass(); + + $this->model = new Manager( + $this->outputConfig, + $this->moduleList ); } public function testIsEnabled() { - $this->_moduleList->expects($this->exactly(2))->method('has')->will($this->returnValueMap([ - ['Module_Exists', true], - ['Module_NotExists', false], - ])); - $this->assertTrue($this->_model->isEnabled('Module_Exists')); - $this->assertFalse($this->_model->isEnabled('Module_NotExists')); - } - - public function testIsOutputEnabledReturnsFalseForDisabledModule() - { - $this->_outputConfig->expects($this->any())->method('isSetFlag')->will($this->returnValue(true)); - $this->assertFalse($this->_model->isOutputEnabled('Disabled_Module')); - } - - /** - * @param bool $configValue - * @param bool $expectedResult - * @dataProvider isOutputEnabledGenericConfigPathDataProvider - */ - public function testIsOutputEnabledGenericConfigPath($configValue, $expectedResult) - { - $this->_moduleList->expects($this->once())->method('has')->will($this->returnValue(true)); - $this->_outputConfig->expects($this->once()) - ->method('isEnabled') - ->with('Module_One') - ->will($this->returnValue($configValue)); - $this->assertEquals($expectedResult, $this->_model->isOutputEnabled('Module_One')); - } + $this->moduleList->expects($this->exactly(2)) + ->method('has') + ->willReturnMap( + [ + ['Module_Exists', true], + ['Module_NotExists', false], + ] + ); - public function isOutputEnabledGenericConfigPathDataProvider() - { - return ['output disabled' => [true, false], 'output enabled' => [false, true]]; - } - - /** - * @param bool $configValue - * @param bool $expectedResult - * @dataProvider isOutputEnabledCustomConfigPathDataProvider - */ - public function testIsOutputEnabledCustomConfigPath($configValue, $expectedResult) - { - $this->_moduleList->expects($this->once())->method('has')->will($this->returnValue(true)); - $this->_outputConfig->expects($this->at(0)) - ->method('isSetFlag') - ->with(self::XML_PATH_OUTPUT_ENABLED) - ->will($this->returnValue($configValue)); - $this->assertEquals($expectedResult, $this->_model->isOutputEnabled('Module_Two')); - } - - public function isOutputEnabledCustomConfigPathDataProvider() - { - return [ - 'path literal, output disabled' => [false, false], - 'path literal, output enabled' => [true, true], - ]; + $this->assertTrue($this->model->isEnabled('Module_Exists')); + $this->assertFalse($this->model->isEnabled('Module_NotExists')); } } diff --git a/lib/internal/Magento/Framework/Setup/FilePermissions.php b/lib/internal/Magento/Framework/Setup/FilePermissions.php index 1ca91b57e2e90ab38afcfecd53dee6f7268ccac6..4cc4a1852f0d725e324e4d18a5a53d7bf2f458a3 100644 --- a/lib/internal/Magento/Framework/Setup/FilePermissions.php +++ b/lib/internal/Magento/Framework/Setup/FilePermissions.php @@ -130,7 +130,7 @@ class FilePermissions } /** - * Check all sub-directories and files except for var/generation and var/di + * Check all sub-directories and files except for generated/code and generated/metadata * * @param string $directory * @return bool diff --git a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php index bebeb7917f5010e768ac6b10ce4ca139de98900d..ee0b51b4b71d0dccae94956fcc44593340bbf992 100644 --- a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php +++ b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php @@ -647,12 +647,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl public function toHtml() { $this->_eventManager->dispatch('view_block_abstract_to_html_before', ['block' => $this]); - if ($this->_scopeConfig->getValue( - 'advanced/modules_disable_output/' . $this->getModuleName(), - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - )) { - return ''; - } + $this->getModuleName(); $html = $this->_loadCache(); if ($html === false) { diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php index b6846b52972e8b12287f727e56866c5452d04dcf..b6fde2c0f6a7fe465a82b47108dd9d9dd57b99a8 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php @@ -3,9 +3,6 @@ * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - namespace Magento\Framework\View\Test\Unit\Element; use Magento\Framework\View\Element\AbstractBlock; @@ -207,13 +204,12 @@ class AbstractBlockTest extends \PHPUnit_Framework_TestCase $moduleName = 'Test'; $this->block->setData('module_name', $moduleName); - $this->eventManagerMock->expects($this->any()) + $this->eventManagerMock->expects($this->exactly(2)) ->method('dispatch') - ->with('view_block_abstract_to_html_before', ['block' => $this->block]); - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with('advanced/modules_disable_output/' . $moduleName, \Magento\Store\Model\ScopeInterface::SCOPE_STORE) - ->willReturn(true); + ->willReturnMap([ + ['view_block_abstract_to_html_before', ['block' => $this->block]], + ['view_block_abstract_to_html_after', ['block' => $this->block]], + ]); $this->assertSame('', $this->block->toHtml()); } @@ -246,10 +242,6 @@ class AbstractBlockTest extends \PHPUnit_Framework_TestCase $this->eventManagerMock->expects($expectsDispatchEvent) ->method('dispatch'); - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with('advanced/modules_disable_output/' . $moduleName, \Magento\Store\Model\ScopeInterface::SCOPE_STORE) - ->willReturn(false); $this->cacheStateMock->expects($this->any()) ->method('isEnabled') ->with(AbstractBlock::CACHE_GROUP) diff --git a/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php b/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php index 4734b99f19247e8763aa29ee0c19f6ae8fdce129..e652bb4d1183481b7c66230eacd7a0013bef3492 100644 --- a/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php +++ b/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php @@ -17,7 +17,7 @@ use Symfony\Component\Console\Output\OutputInterface; class UpgradeCommand extends AbstractSetupCommand { /** - * Option to skip deletion of var/generation directory + * Option to skip deletion of generated/code directory */ const INPUT_KEY_KEEP_GENERATED = 'keep-generated'; diff --git a/setup/src/Magento/Setup/Console/CompilerPreparation.php b/setup/src/Magento/Setup/Console/CompilerPreparation.php index df32f9b018a0f2bbb8103ae9556c8863d17ff286..e986749ee6efb04a26aa73ddbd59fb933cc003a2 100644 --- a/setup/src/Magento/Setup/Console/CompilerPreparation.php +++ b/setup/src/Magento/Setup/Console/CompilerPreparation.php @@ -1,49 +1,62 @@ <?php -/*** +/** * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Setup\Console; - use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Console\GenerationDirectoryAccess; +use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Filesystem\Driver\File; +use Magento\Framework\Phrase; use Magento\Setup\Console\Command\DiCompileCommand; use Magento\Setup\Mvc\Bootstrap\InitParamListener; use Symfony\Component\Console\Input\ArgvInput; +use Zend\ServiceManager\ServiceManager; class CompilerPreparation { - /** @var \Zend\ServiceManager\ServiceManager */ + /** + * @var ServiceManager + */ private $serviceManager; - /** @var ArgvInput */ + /** + * @var ArgvInput + */ private $input; - /** @var File */ + /** + * @var File + */ private $filesystemDriver; /** - * @param \Zend\ServiceManager\ServiceManager $serviceManager + * @var GenerationDirectoryAccess + */ + private $generationDirectoryAccess; + + /** + * @param ServiceManager $serviceManager * @param ArgvInput $input * @param File $filesystemDriver */ public function __construct( - \Zend\ServiceManager\ServiceManager $serviceManager, - \Symfony\Component\Console\Input\ArgvInput $input, - \Magento\Framework\Filesystem\Driver\File $filesystemDriver + ServiceManager $serviceManager, + ArgvInput $input, + File $filesystemDriver ) { - $this->serviceManager = $serviceManager; - $this->input = $input; + $this->serviceManager = $serviceManager; + $this->input = $input; $this->filesystemDriver = $filesystemDriver; } /** - * Determine whether a CLI command is for compilation, and if so, clear the directory + * Determine whether a CLI command is for compilation, and if so, clear the directory. * - * @throws \Magento\Framework\Exception\FileSystemException + * @throws FileSystemException if generation directory is read-only * @return void */ public function handleCompilerEnvironment() @@ -63,10 +76,30 @@ class CompilerPreparation $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATION); $compileDirList[] = $directoryList->getPath(DirectoryList::DI); + if (!$this->getGenerationDirectoryAccess()->check()) { + throw new FileSystemException( + new Phrase('Generation directory can not be written.') + ); + } + foreach ($compileDirList as $compileDir) { if ($this->filesystemDriver->isExists($compileDir)) { $this->filesystemDriver->deleteDirectory($compileDir); } } } + + /** + * Retrieves generation directory access checker. + * + * @return GenerationDirectoryAccess the generation directory access checker + */ + private function getGenerationDirectoryAccess() + { + if (null === $this->generationDirectoryAccess) { + $this->generationDirectoryAccess = new GenerationDirectoryAccess($this->serviceManager); + } + + return $this->generationDirectoryAccess; + } } diff --git a/setup/src/Magento/Setup/Model/Installer.php b/setup/src/Magento/Setup/Model/Installer.php index 0918ee66fbb684e5553a857cda7e5807c417d6b8..088b40aa4decbf24fb11ab65dca17c7ca726e445 100644 --- a/setup/src/Magento/Setup/Model/Installer.php +++ b/setup/src/Magento/Setup/Model/Installer.php @@ -1262,7 +1262,7 @@ class Installer } /** - * Clear var/generation and reset object manager + * Clear generated/code and reset object manager * * @return void */ diff --git a/setup/src/Magento/Setup/Module/Di/Code/Reader/Decorator/Interceptions.php b/setup/src/Magento/Setup/Module/Di/Code/Reader/Decorator/Interceptions.php index 8a415dd65e43f4221abfd90d414dcc44fab617f3..5480a5953ce583fb50b27f6bbcd872acdcfdf0f7 100644 --- a/setup/src/Magento/Setup/Module/Di/Code/Reader/Decorator/Interceptions.php +++ b/setup/src/Magento/Setup/Module/Di/Code/Reader/Decorator/Interceptions.php @@ -6,6 +6,7 @@ namespace Magento\Setup\Module\Di\Code\Reader\Decorator; use Magento\Setup\Module\Di\Compiler\Log\Log; +use Magento\Framework\App\Filesystem\DirectoryList; /** * Class Interceptions @@ -71,7 +72,8 @@ class Interceptions implements \Magento\Setup\Module\Di\Code\Reader\ClassesScann $nameList = []; foreach ($this->classesScanner->getList($path) as $className) { try { - if (!strpos($path, 'generation')) { // validate all classes except classes in var/generation dir + // validate all classes except classes in generated/code dir + if (!strpos($path, DirectoryList::getDefaultConfig()[DirectoryList::GENERATION][DirectoryList::PATH])) { $this->validator->validate($className); } $nameList[] = $className; diff --git a/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php b/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php index 813cb5dccc98f7ac7d34b0fdc28ff7769070bbaf..93118211062eddac065cdfe4b75bcb8f310421f3 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php @@ -1,48 +1,69 @@ <?php -/*** +/** * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Setup\Test\Unit\Console; - +use Magento\Framework\Console\GenerationDirectoryAccess; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Setup\Console\Command\DiCompileCommand; use Magento\Setup\Mvc\Bootstrap\InitParamListener; +use Magento\Framework\Filesystem\Driver\File; use Symfony\Component\Console\Input\ArgvInput; +use Zend\ServiceManager\ServiceManager; +use Magento\Setup\Console\CompilerPreparation; +use PHPUnit_Framework_MockObject_MockObject as Mock; class CompilerPreparationTest extends \PHPUnit_Framework_TestCase { - /** @var \Magento\Setup\Console\CompilerPreparation */ + /** + * @var CompilerPreparation|Mock + */ private $model; - /** @var \Zend\ServiceManager\ServiceManager | \PHPUnit_Framework_MockObject_MockObject */ + /** + * @var ServiceManager|Mock + */ private $serviceManagerMock; - /** @var \Symfony\Component\Console\Input\ArgvInput | \PHPUnit_Framework_MockObject_MockObject */ + /** + * @var ArgvInput|Mock + */ private $inputMock; - /** @var \Magento\Framework\Filesystem\Driver\File | \PHPUnit_Framework_MockObject_MockObject */ + /** + * @var File|Mock + */ private $filesystemDriverMock; + /** + * @var GenerationDirectoryAccess|Mock + */ + private $generationDirectoryAccessMock; + public function setUp() { - $this->serviceManagerMock = $this->getMockBuilder(\Zend\ServiceManager\ServiceManager::class) + $this->serviceManagerMock = $this->getMockBuilder(ServiceManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->inputMock = $this->getMockBuilder(ArgvInput::class) ->disableOriginalConstructor() ->getMock(); - $this->inputMock = $this->getMockBuilder(\Symfony\Component\Console\Input\ArgvInput::class) + $this->filesystemDriverMock = $this->getMockBuilder(File::class) ->disableOriginalConstructor() ->getMock(); - $this->filesystemDriverMock = $this->getMockBuilder(\Magento\Framework\Filesystem\Driver\File::class) + $this->generationDirectoryAccessMock = $this->getMockBuilder(GenerationDirectoryAccess::class) ->disableOriginalConstructor() ->getMock(); + $this->model = (new ObjectManager($this))->getObject( - \Magento\Setup\Console\CompilerPreparation::class, + CompilerPreparation::class, [ 'serviceManager' => $this->serviceManagerMock, 'input' => $this->inputMock, - 'filesystemDriver' => $this->filesystemDriverMock + 'filesystemDriver' => $this->filesystemDriverMock, + 'generationDirectoryAccess' => $this->generationDirectoryAccessMock, ] ); } @@ -56,21 +77,31 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase */ public function testClearGenerationDirWhenNeeded($commandName, $isCompileCommand, $isHelpOption, $dirExists = false) { - $this->inputMock->expects($this->once())->method('getFirstArgument')->willReturn($commandName); + $this->inputMock->expects($this->once()) + ->method('getFirstArgument') + ->willReturn($commandName); $this->inputMock->expects($this->atLeastOnce()) ->method('hasParameterOption') - ->with( - $this->logicalOr('--help', '-h') - )->willReturn($isHelpOption); + ->with($this->logicalOr('--help', '-h')) + ->willReturn($isHelpOption); + if ($isCompileCommand && !$isHelpOption) { $this->filesystemDriverMock->expects($this->exactly(2)) ->method('isExists') ->willReturn($dirExists); - $this->filesystemDriverMock->expects($this->exactly(((int)$dirExists) * 2))->method('deleteDirectory'); + $this->filesystemDriverMock->expects($this->exactly(((int)$dirExists) * 2)) + ->method('deleteDirectory'); } else { - $this->filesystemDriverMock->expects($this->never())->method('isExists'); - $this->filesystemDriverMock->expects($this->never())->method('deleteDirectory'); + $this->filesystemDriverMock->expects($this->never()) + ->method('isExists'); + $this->filesystemDriverMock->expects($this->never()) + ->method('deleteDirectory'); } + + $this->generationDirectoryAccessMock->expects($this->any()) + ->method('check') + ->willReturn(true); + $this->model->handleCompilerEnvironment(); } @@ -108,10 +139,6 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase $customGenerationDirectory = '/custom/generated/code/directory'; $defaultDiDirectory = '/custom/di/directory'; $mageInitParams = ['MAGE_DIRS' => ['generation' => ['path' => $customGenerationDirectory]]]; - - $this->inputMock->expects($this->once()) - ->method('getFirstArgument') - ->willReturn(DiCompileCommand::NAME); $dirValueMap = [ [ $customGenerationDirectory, @@ -122,16 +149,24 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase true ] ]; - // Filesystem mock - $this->filesystemDriverMock->expects($this->exactly(2))->method('isExists')->willReturn(true); + + $this->inputMock->expects($this->once()) + ->method('getFirstArgument') + ->willReturn(DiCompileCommand::NAME); + $this->filesystemDriverMock->expects($this->exactly(2)) + ->method('isExists') + ->willReturn(true); $this->filesystemDriverMock->expects($this->exactly(2)) ->method('deleteDirectory') - ->will($this->returnValueMap($dirValueMap)); - + ->willReturnMap($dirValueMap); $this->serviceManagerMock->expects($this->once()) ->method('get') ->with(InitParamListener::BOOTSTRAP_PARAM) ->willReturn($mageInitParams); + $this->generationDirectoryAccessMock->expects($this->once()) + ->method('check') + ->willReturn(true); + $this->model->handleCompilerEnvironment(); } @@ -139,10 +174,6 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase { $customGenerationDirectory = '/custom/generated/code/directory'; $customDiDirectory = '/custom/di/directory'; - - $this->inputMock->expects($this->once()) - ->method('getFirstArgument') - ->willReturn(DiCompileCommand::NAME); $dirResultMap = [ [ $this->logicalNot($this->equalTo($customGenerationDirectory)), @@ -154,10 +185,18 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase ] ]; - $this->filesystemDriverMock->expects($this->exactly(2))->method('isExists')->willReturn(true); + $this->inputMock->expects($this->once()) + ->method('getFirstArgument') + ->willReturn(DiCompileCommand::NAME); + $this->filesystemDriverMock->expects($this->exactly(2)) + ->method('isExists') + ->willReturn(true); $this->filesystemDriverMock->expects($this->exactly(2)) ->method('deleteDirectory') - ->will($this->returnValueMap($dirResultMap)); + ->willReturnMap($dirResultMap); + $this->generationDirectoryAccessMock->expects($this->once()) + ->method('check') + ->willReturn(true); $this->model->handleCompilerEnvironment(); } diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/DirectoryTest.php b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/DirectoryTest.php index b34322ec9992a650f87ad973f94bb07a27c858fb..0ccc4d9f9b35fe82e3775d2934c27dfecb10c382 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/DirectoryTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/DirectoryTest.php @@ -66,7 +66,7 @@ class DirectoryTest extends \PHPUnit_Framework_TestCase $this->classReaderMock, $this->classesScanner, $this->validatorMock, - '/var/generation' + '/generated/code' ); } @@ -111,7 +111,7 @@ class DirectoryTest extends \PHPUnit_Framework_TestCase public function testGetListNoValidation() { - $path = '/var/generation'; + $path = '/generated/code'; $classes = ['NameSpace1\ClassName1', 'NameSpace1\ClassName2']; diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php index 0591b11b9628c0117ede3b88201722ed6d86da2a..733ba0b85866e38d24f17e6b2d031e75bab17ce3 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/Code/Reader/InstancesNamesList/InterceptionsTest.php @@ -97,7 +97,7 @@ class InterceptionsTest extends \PHPUnit_Framework_TestCase public function testGetListNoValidation() { - $path = '/var/generation'; + $path = '/generated/code'; $classes = ['NameSpace1\ClassName1', 'NameSpace1\ClassName2'];