diff --git a/app/code/Magento/Catalog/Block/Category/Plugin/PriceBoxTags.php b/app/code/Magento/Catalog/Block/Category/Plugin/PriceBoxTags.php index 38fecf71e240c..aed1624b955f6 100644 --- a/app/code/Magento/Catalog/Block/Category/Plugin/PriceBoxTags.php +++ b/app/code/Magento/Catalog/Block/Category/Plugin/PriceBoxTags.php @@ -12,6 +12,7 @@ use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\Pricing\Render\PriceBox; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Tax\Model\Calculation as TaxCalculation; class PriceBoxTags { @@ -35,6 +36,11 @@ class PriceBoxTags */ private $scopeResolver; + /** + * @var TaxCalculation + */ + private $taxCalculation; + /** * PriceBoxTags constructor. * @param PriceCurrencyInterface $priceCurrency @@ -58,8 +64,6 @@ public function __construct( * @param PriceBox $subject * @param string $result * @return string - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterGetCacheKey(PriceBox $subject, $result) { @@ -71,7 +75,59 @@ public function afterGetCacheKey(PriceBox $subject, $result) $this->dateTime->scopeDate($this->scopeResolver->getScope()->getId())->format('Ymd'), $this->scopeResolver->getScope()->getId(), $this->customerSession->getCustomerGroupId(), + $this->getTaxRateIds($subject), ] ); } + + /** + * @param PriceBox $subject + * @return string + */ + private function getTaxRateIds(PriceBox $subject) + { + $rateIds = []; + + $customerSession = $this->customerSession; + $billingAddress = $customerSession->getDefaultTaxBillingAddress(); + $shippingAddress = $customerSession->getDefaultTaxShippingAddress(); + $customerTaxClassId = $customerSession->getCustomerTaxClassId(); + + if (!empty($billingAddress)) { + $billingAddress = new \Magento\Framework\DataObject($billingAddress); + } + if (!empty($shippingAddress)) { + $shippingAddress = new \Magento\Framework\DataObject($shippingAddress); + } + + if (!empty($billingAddress) || !empty($shippingAddress)) { + $rateRequest = $this->getTaxCalculation()->getRateRequest( + $billingAddress, + $shippingAddress, + $customerTaxClassId, + $this->scopeResolver->getScope()->getId(), + $this->customerSession->getCustomerId() + ); + + $rateRequest->setProductClassId($subject->getSaleableItem()->getTaxClassId()); + $rateIds = $this->getTaxCalculation()->getResource()->getRateIds($rateRequest); + } + + return implode('_', $rateIds); + } + + /** + * Get the TaxCalculation model + * + * @return \Magento\Tax\Model\Calculation + * + * @deprecated + */ + private function getTaxCalculation() + { + if ($this->taxCalculation === null) { + $this->taxCalculation = \Magento\Framework\App\ObjectManager::getInstance()->get(TaxCalculation::class); + } + return $this->taxCalculation; + } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php index b1b1bd5e1ee32..3279a620c75b3 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php @@ -5,6 +5,10 @@ */ namespace Magento\Catalog\Model\Indexer\Product\Category\Action; +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Product; +use Magento\Framework\Indexer\CacheContext; + class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction { /** @@ -14,6 +18,11 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio */ protected $limitationByProducts; + /** + * @var \Magento\Framework\Indexer\CacheContext + */ + private $cacheContext; + /** * Refresh entities index * @@ -30,9 +39,43 @@ public function execute(array $entityIds = [], $useTempTable = false) $this->reindex(); + $this->registerProducts($entityIds); + $this->registerCategories($entityIds); + return $this; } + /** + * Register affected products + * + * @param array $entityIds + * @return void + */ + private function registerProducts($entityIds) + { + $this->getCacheContext()->registerEntities(Product::CACHE_TAG, $entityIds); + } + + /** + * Register categories assigned to products + * + * @param array $entityIds + * @return void + */ + private function registerCategories($entityIds) + { + $categories = $this->connection->fetchCol( + $this->connection->select() + ->from($this->getMainTable(), ['category_id']) + ->where('product_id IN (?)', $entityIds) + ->distinct() + ); + + if ($categories) { + $this->getCacheContext()->registerEntities(Category::CACHE_TAG, $categories); + } + } + /** * Remove index entries before reindexation * @@ -91,4 +134,18 @@ protected function isRangingNeeded() { return false; } + + /** + * Get cache context + * + * @return \Magento\Framework\Indexer\CacheContext + * @deprecated + */ + private function getCacheContext() + { + if ($this->cacheContext === null) { + $this->cacheContext = \Magento\Framework\App\ObjectManager::getInstance()->get(CacheContext::class); + } + return $this->cacheContext; + } } diff --git a/app/code/Magento/Catalog/Plugin/Model/Product/Action/UpdateAttributesFlushCache.php b/app/code/Magento/Catalog/Plugin/Model/Product/Action/UpdateAttributesFlushCache.php index e2c656bc39e60..010c5f4d0e2e8 100644 --- a/app/code/Magento/Catalog/Plugin/Model/Product/Action/UpdateAttributesFlushCache.php +++ b/app/code/Magento/Catalog/Plugin/Model/Product/Action/UpdateAttributesFlushCache.php @@ -5,7 +5,6 @@ */ namespace Magento\Catalog\Plugin\Model\Product\Action; -use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Action; use Magento\Framework\Indexer\CacheContext; use Magento\Framework\Event\ManagerInterface as EventManager; @@ -36,26 +35,28 @@ public function __construct( /** * @param Action $subject - * @param \Closure $proceed - * @param array $productIds - * @param array $attrData - * @param int $storeId + * @param Action $result * @return Action * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function aroundUpdateAttributes( - Action $subject, - \Closure $proceed, - $productIds, - $attrData, - $storeId + public function afterUpdateAttributes( + \Magento\Catalog\Model\Product\Action $subject, + \Magento\Catalog\Model\Product\Action $result ) { - $returnValue = $proceed($productIds, $attrData, $storeId); - - $this->cacheContext->registerEntities(Product::CACHE_TAG, $productIds); $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]); + return $result; + } - return $returnValue; + /** + * @param Action $subject + * @return void + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterUpdateWebsites( + \Magento\Catalog\Model\Product\Action $subject + ) { + $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Category/Plugin/PriceBoxTagsTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Category/Plugin/PriceBoxTagsTest.php new file mode 100644 index 0000000000000..60c7b5eada1aa --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Block/Category/Plugin/PriceBoxTagsTest.php @@ -0,0 +1,141 @@ +priceCurrencyInterface = $this->getMockBuilder( + \Magento\Framework\Pricing\PriceCurrencyInterface::class + )->getMock(); + $this->timezoneInterface = $this->getMockBuilder( + \Magento\Framework\Stdlib\DateTime\TimezoneInterface::class + )->getMock(); + $this->scopeResolverInterface = $this->getMockBuilder( + \Magento\Framework\App\ScopeResolverInterface::class + ) + ->getMockForAbstractClass(); + $this->session = $this->getMockBuilder(\Magento\Customer\Model\Session::class)->disableOriginalConstructor() + ->setMethods( + [ + 'getCustomerGroupId', + 'getDefaultTaxBillingAddress', + 'getDefaultTaxShippingAddress', + 'getCustomerTaxClassId', + 'getCustomerId' + ] + ) + ->getMock(); + $this->taxCalculation = $this->getMockBuilder(\Magento\Tax\Model\Calculation::class) + ->disableOriginalConstructor()->getMock(); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->priceBoxTags = $objectManager->getObject( + \Magento\Catalog\Block\Category\Plugin\PriceBoxTags::class, + [ + 'priceCurrency' => $this->priceCurrencyInterface, + 'dateTime' => $this->timezoneInterface, + 'scopeResolver' => $this->scopeResolverInterface, + 'customerSession' => $this->session, + 'taxCalculation' => $this->taxCalculation + ] + ); + } + + public function testAfterGetCacheKey() + { + $date = date('Ymd'); + $currencySymbol = '$'; + $result = 'result_string'; + $billingAddress = ['billing_address']; + $shippingAddress = ['shipping_address']; + $scopeId = 1; + $customerGroupId = 2; + $customerTaxClassId = 3; + $customerId = 4; + $rateIds = [5,6]; + $expected = implode( + '-', + [ + $result, + $currencySymbol, + $date, + $scopeId, + $customerGroupId, + implode('_', $rateIds) + ] + ); + $priceBox = $this->getMockBuilder(\Magento\Framework\Pricing\Render\PriceBox::class) + ->disableOriginalConstructor()->getMock(); + $this->priceCurrencyInterface->expects($this->once())->method('getCurrencySymbol')->willReturn($currencySymbol); + $scope = $this->getMockBuilder(\Magento\Framework\App\ScopeInterface::class)->getMock(); + $this->scopeResolverInterface->expects($this->any())->method('getScope')->willReturn($scope); + $scope->expects($this->any())->method('getId')->willReturn($scopeId); + $dateTime = $this->getMockBuilder(\DateTime::class)->getMock(); + $this->timezoneInterface->expects($this->any())->method('scopeDate')->with($scopeId)->willReturn($dateTime); + $dateTime->expects($this->any())->method('format')->with('Ymd')->willReturn($date); + $this->session->expects($this->once())->method('getCustomerGroupId')->willReturn($customerGroupId); + $this->session->expects($this->once())->method('getDefaultTaxBillingAddress')->willReturn($billingAddress); + $this->session->expects($this->once())->method('getDefaultTaxShippingAddress')->willReturn($shippingAddress); + $this->session->expects($this->once())->method('getCustomerTaxClassId') + ->willReturn($customerTaxClassId); + $this->session->expects($this->once())->method('getCustomerId')->willReturn($customerId); + $rateRequest = $this->getMockBuilder(\Magento\Framework\DataObject::class)->getMock(); + $this->taxCalculation->expects($this->once())->method('getRateRequest')->with( + new \Magento\Framework\DataObject($billingAddress), + new \Magento\Framework\DataObject($shippingAddress), + $customerTaxClassId, + $scopeId, + $customerId + )->willReturn($rateRequest); + $salableInterface = $this->getMockBuilder(\Magento\Framework\Pricing\SaleableInterface::class) + ->setMethods(['getTaxClassId']) + ->getMockForAbstractClass(); + $priceBox->expects($this->once())->method('getSaleableItem')->willReturn($salableInterface); + $salableInterface->expects($this->once())->method('getTaxClassId')->willReturn($customerTaxClassId); + $resource = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\AbstractResource::class) + ->setMethods(['getRateIds']) + ->getMockForAbstractClass(); + $this->taxCalculation->expects($this->once())->method('getResource')->willReturn($resource); + $resource->expects($this->once())->method('getRateIds')->with($rateRequest)->willReturn($rateIds); + + $this->assertEquals($expected, $this->priceBoxTags->afterGetCacheKey($priceBox, $result)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Plugin/Model/Product/Action/UpdateAttributesFlushCacheTest.php b/app/code/Magento/Catalog/Test/Unit/Plugin/Model/Product/Action/UpdateAttributesFlushCacheTest.php index 3515addebdcaa..ff3e3d2b7ce14 100644 --- a/app/code/Magento/Catalog/Test/Unit/Plugin/Model/Product/Action/UpdateAttributesFlushCacheTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Plugin/Model/Product/Action/UpdateAttributesFlushCacheTest.php @@ -9,38 +9,41 @@ class UpdateAttributesFlushCacheTest extends \PHPUnit_Framework_TestCase { - public function testAroundUpdateAttributes() - { - $productIds = [1, 2, 3]; - $attrData = []; - $storeId = 1; - - $productActionMock = $this->getMock('Magento\Catalog\Model\Product\Action', [], [], '', false); - - $cacheContextMock = $this->getMock('Magento\Framework\Indexer\CacheContext', [], [], '', false); - $cacheContextMock->expects($this->once()) - ->method('registerEntities') - ->with(Product::CACHE_TAG, $productIds); + /** + * @var \Magento\Catalog\Plugin\Model\Product\Action\UpdateAttributesFlushCache + */ + private $model; + protected function setUp() + { + $cacheContextMock = $this->getMock(\Magento\Framework\Indexer\CacheContext::class, [], [], '', false); - $eventManagerMock = $this->getMock('Magento\Framework\Event\ManagerInterface'); + $eventManagerMock = $this->getMock(\Magento\Framework\Event\ManagerInterface::class); $eventManagerMock->expects($this->once()) ->method('dispatch') ->with('clean_cache_by_tags', ['object' => $cacheContextMock]); $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $model = $objectManager->getObject( - 'Magento\Catalog\Plugin\Model\Product\Action\UpdateAttributesFlushCache', + $this->model = $objectManager->getObject( + \Magento\Catalog\Plugin\Model\Product\Action\UpdateAttributesFlushCache::class, [ 'cacheContext' => $cacheContextMock, 'eventManager' => $eventManagerMock, ] ); + } - $closureMock = function () use ($productActionMock) { - return $productActionMock; - }; + public function testAroundUpdateAttributes() + { + /** @var \Magento\Catalog\Model\Product\Action $productActionMock */ + $productActionMock = $this->getMock(\Magento\Catalog\Model\Product\Action::class, [], [], '', false); + $this->model->afterUpdateAttributes($productActionMock, $productActionMock); + } - $model->aroundUpdateAttributes($productActionMock, $closureMock, $productIds, $attrData, $storeId); + public function testAroundUpdateWebsites() + { + /** @var \Magento\Catalog\Model\Product\Action $productActionMock */ + $productActionMock = $this->getMock(\Magento\Catalog\Model\Product\Action::class, [], [], '', false); + $this->model->afterUpdateWebsites($productActionMock, $productActionMock); } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/ActionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/ActionTest.php new file mode 100644 index 0000000000000..6d27c1024f227 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/ActionTest.php @@ -0,0 +1,94 @@ +get(\Magento\Framework\Indexer\IndexerRegistry::class); + $indexerRegistry->get(Fulltext::INDEXER_ID)->setScheduled(true); + } + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + /** @var \Magento\Framework\App\Cache\StateInterface $cacheState */ + $cacheState = $this->objectManager->get(\Magento\Framework\App\Cache\StateInterface::class); + $cacheState->setEnabled(\Magento\PageCache\Model\Cache\Type::TYPE_IDENTIFIER, true); + + $this->action = $this->objectManager->create(\Magento\Catalog\Model\Product\Action::class); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoDataFixture Magento/Store/_files/core_second_third_fixturestore.php + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testUpdateWebsites() + { + /** @var \Magento\Store\Api\WebsiteRepositoryInterface $websiteRepository */ + $websiteRepository = $this->objectManager->create(\Magento\Store\Api\WebsiteRepositoryInterface::class); + + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + + /** @var \Magento\Framework\App\CacheInterface $cacheManager */ + $pageCache = $this->objectManager->create(\Magento\PageCache\Model\Cache\Type::class); + + /** @var \Magento\Catalog\Model\Product $product */ + $product = $productRepository->get('simple'); + foreach ($product->getCategoryIds() as $categoryId) { + $pageCache->save( + 'test_data', + 'test_data_category_id_' . $categoryId, + [\Magento\Catalog\Model\Category::CACHE_TAG . '_' . $categoryId] + ); + $this->assertEquals('test_data', $pageCache->load('test_data_category_id_' . $categoryId)); + } + + $websites = $websiteRepository->getList(); + $websiteIds = []; + foreach ($websites as $websiteCode => $website) { + if (in_array($websiteCode, ['secondwebsite', 'thirdwebsite'])) { + $websiteIds[] = $website->getId(); + } + } + + $this->action->updateWebsites([$product->getId()], $websiteIds, 'add'); + + foreach ($product->getCategoryIds() as $categoryId) { + $this->assertEmpty( + $pageCache->load('test_data_category_id_' . $categoryId) + ); + } + } + + public static function tearDownAfterClass() + { + /** @var \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry */ + $indexerRegistry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Framework\Indexer\IndexerRegistry::class); + $indexerRegistry->get(Fulltext::INDEXER_ID)->setScheduled(false); + } +}