diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 6df07e7b3a9fa..bfbdd9e4688d9 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -2509,13 +2509,15 @@ public function setTypeId($typeId) /** * {@inheritdoc} * - * @return \Magento\Catalog\Api\Data\ProductExtensionInterface|null + * @return \Magento\Catalog\Api\Data\ProductExtensionInterface */ public function getExtensionAttributes() { $extensionAttributes = $this->_getExtensionAttributes(); - if (!$extensionAttributes) { - return $this->extensionAttributesFactory->create(\Magento\Catalog\Api\Data\ProductInterface::class); + if (null === $extensionAttributes) { + /** @var \Magento\Catalog\Api\Data\ProductExtensionInterface $extensionAttributes */ + $extensionAttributes = $this->extensionAttributesFactory->create(ProductInterface::class); + $this->setExtensionAttributes($extensionAttributes); } return $extensionAttributes; } @@ -2639,4 +2641,68 @@ public function setAssociatedProductIds(array $productIds) $this->getExtensionAttributes()->setConfigurableProductLinks($productIds); return $this; } + + /** + * Get quantity and stock status data + * + * @return array|null + * + * @deprecated as Product model shouldn't be responsible for stock status + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ + public function getQuantityAndStockStatus() + { + return $this->getData('quantity_and_stock_status'); + } + + /** + * Set quantity and stock status data + * + * @param array $quantityAndStockStatusData + * @return $this + * + * @deprecated as Product model shouldn't be responsible for stock status + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ + public function setQuantityAndStockStatus($quantityAndStockStatusData) + { + $this->setData('quantity_and_stock_status', $quantityAndStockStatusData); + return $this; + } + + /** + * Get stock data + * + * @return array|null + * + * @deprecated as Product model shouldn't be responsible for stock status + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ + public function getStockData() + { + return $this->getData('stock_data'); + } + + /** + * Set stock data + * + * @param array $stockData + * @return $this + * + * @deprecated as Product model shouldn't be responsible for stock status + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ + public function setStockData($stockData) + { + $this->setData('stock_data', $stockData); + return $this; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php index 4df4691117201..eba000efa0969 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php @@ -3,13 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Catalog\Model\Product\Attribute\Backend; use Magento\Catalog\Model\Product; /** * Quantity and Stock Status attribute processing + * + * @deprecated as this attribute should be removed + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process */ class Stock extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend { @@ -47,25 +51,6 @@ public function afterLoad($object) return parent::afterLoad($object); } - /** - * Prepare inventory data from custom attribute - * - * @param Product $object - * @return void - */ - public function beforeSave($object) - { - $stockData = $object->getData($this->getAttribute()->getAttributeCode()); - if (isset($stockData['qty']) && $stockData['qty'] === '') { - $stockData['qty'] = null; - } - if ($object->getStockData() !== null && $stockData !== null) { - $object->setStockData(array_replace((array)$object->getStockData(), (array)$stockData)); - } - $object->unsetData($this->getAttribute()->getAttributeCode()); - parent::beforeSave($object); - } - /** * Validate * diff --git a/app/code/Magento/Catalog/Model/Product/Copier.php b/app/code/Magento/Catalog/Model/Product/Copier.php index 176a15c895261..efe20b4003d02 100644 --- a/app/code/Magento/Catalog/Model/Product/Copier.php +++ b/app/code/Magento/Catalog/Model/Product/Copier.php @@ -59,7 +59,9 @@ public function copy(\Magento\Catalog\Model\Product $product) /** @var \Magento\Catalog\Model\Product $duplicate */ $duplicate = $this->productFactory->create(); - $duplicate->setData($product->getData()); + $productData = $product->getData(); + $productData = $this->removeStockItem($productData); + $duplicate->setData($productData); $duplicate->setOptions([]); $duplicate->setIsDuplicate(true); $duplicate->setOriginalLinkId($product->getData($metadata->getLinkField())); @@ -116,4 +118,21 @@ private function getMetadataPool() } return $this->metadataPool; } + + /** + * Remove stock item + * + * @param array $productData + * @return array + */ + private function removeStockItem(array $productData) + { + if (isset($productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY])) { + $extensionAttributes = $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]; + if (null !== $extensionAttributes->getStockItem()) { + $extensionAttributes->setData('stock_item', null); + } + } + return $productData; + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php index cc406d2def352..4f481caabf1df 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php @@ -74,68 +74,4 @@ public function testAfterLoad() $this->assertEquals(1, $data[self::ATTRIBUTE_NAME]['is_in_stock']); $this->assertEquals(5, $data[self::ATTRIBUTE_NAME]['qty']); } - - public function testBeforeSave() - { - $object = new \Magento\Framework\DataObject( - [ - self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => 5], - 'stock_data' => ['is_in_stock' => 2, 'qty' => 2], - ] - ); - $stockData = $object->getStockData(); - $this->assertEquals(2, $stockData['is_in_stock']); - $this->assertEquals(2, $stockData['qty']); - $this->assertNotEmpty($object->getData(self::ATTRIBUTE_NAME)); - - $this->model->beforeSave($object); - - $stockData = $object->getStockData(); - $this->assertEquals(1, $stockData['is_in_stock']); - $this->assertEquals(5, $stockData['qty']); - $this->assertNull($object->getData(self::ATTRIBUTE_NAME)); - } - - public function testBeforeSaveQtyIsEmpty() - { - $object = new \Magento\Framework\DataObject( - [ - self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => ''], - 'stock_data' => ['is_in_stock' => 2, 'qty' => ''], - ] - ); - - $this->model->beforeSave($object); - - $stockData = $object->getStockData(); - $this->assertNull($stockData['qty']); - } - - public function testBeforeSaveQtyIsZero() - { - $object = new \Magento\Framework\DataObject( - [ - self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => 0], - 'stock_data' => ['is_in_stock' => 2, 'qty' => 0], - ] - ); - - $this->model->beforeSave($object); - - $stockData = $object->getStockData(); - $this->assertEquals(0, $stockData['qty']); - } - - public function testBeforeSaveNoStockData() - { - $object = new \Magento\Framework\DataObject( - [ - self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => 0] - ] - ); - - $this->model->beforeSave($object); - $this->assertNull($object->getStockData()); - $this->assertNull($object->getData(self::ATTRIBUTE_NAME)); - } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php index 8978af9edc409..bdedb6c72aadf 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php @@ -5,8 +5,12 @@ */ namespace Magento\Catalog\Test\Unit\Model\Product; +use Magento\Catalog\Api\Data\ProductInterface; use \Magento\Catalog\Model\Product\Copier; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class CopierTest extends \PHPUnit_Framework_TestCase { /** @@ -80,10 +84,28 @@ protected function setUp() public function testCopy() { + $stockItem = $this->getMockBuilder(\Magento\CatalogInventory\Api\Data\StockItemInterface::class) + ->getMock(); + $extensionAttributes = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtension::class) + ->setMethods(['getStockItem', 'setData']) + ->getMock(); + $extensionAttributes + ->expects($this->once()) + ->method('getStockItem') + ->willReturn($stockItem); + $extensionAttributes + ->expects($this->once()) + ->method('setData') + ->with('stock_item', null); + + $productData = [ + 'product data' => ['product data'], + ProductInterface::EXTENSION_ATTRIBUTES_KEY => $extensionAttributes, + ]; $this->productMock->expects($this->atLeastOnce())->method('getWebsiteIds'); $this->productMock->expects($this->atLeastOnce())->method('getCategoryIds'); $this->productMock->expects($this->any())->method('getData')->willReturnMap([ - ['', null, 'product data'], + ['', null, $productData], ['linkField', null, '1'], ]); @@ -135,7 +157,7 @@ public function testCopy() )->with( \Magento\Store\Model\Store::DEFAULT_STORE_ID ); - $duplicateMock->expects($this->once())->method('setData')->with('product data'); + $duplicateMock->expects($this->once())->method('setData')->with($productData); $this->copyConstructorMock->expects($this->once())->method('build')->with($this->productMock, $duplicateMock); $duplicateMock->expects($this->once())->method('getUrlKey')->willReturn('urk-key-1'); $duplicateMock->expects($this->once())->method('setUrlKey')->with('urk-key-2'); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index adb0d037067dc..2377b2313b9a7 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -195,6 +195,11 @@ class ProductTest extends \PHPUnit_Framework_TestCase */ private $collectionFactoryMock; + /** + * @var ProductExtensionInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $extensionAttributes; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -392,8 +397,16 @@ protected function setUp() ->setMethods(['create']) ->getMock(); $this->mediaConfig = $this->getMock(\Magento\Catalog\Model\Product\Media\Config::class, [], [], '', false); - $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->extensionAttributes = $this->getMockBuilder(ProductExtensionInterface::class) + ->setMethods(['getStockItem']) + ->getMock(); + $this->extensionAttributesFactory + ->expects($this->any()) + ->method('create') + ->willReturn($this->extensionAttributes); + + $this->objectManagerHelper = new ObjectManagerHelper($this); $this->model = $this->objectManagerHelper->getObject( \Magento\Catalog\Model\Product::class, [ diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php index b823ddb68e911..8967147968f15 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php @@ -41,7 +41,6 @@ public function __construct( parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); $this->request = $request; $this->collection = $collectionFactory->create(); - $this->collection->setExcludeSetFilter((int)$this->request->getParam('template_id', 0)); } /** @@ -49,6 +48,9 @@ public function __construct( */ public function getData() { + $this->collection->setExcludeSetFilter((int)$this->request->getParam('template_id', 0)); + $this->collection->getSelect()->setPart('order', []); + $items = []; foreach ($this->getCollection()->getItems() as $attribute) { $items[] = $attribute->toArray(); diff --git a/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php b/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php index e22be4d004aad..64609da69d48b 100644 --- a/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php +++ b/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php @@ -4,31 +4,28 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\CatalogInventory\Model\Plugin; +/** + * @deprecated Stock Item as a part of ExtensionAttributes is deprecated + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ class AfterProductLoad { /** * @var \Magento\CatalogInventory\Api\StockRegistryInterface */ - protected $stockRegistry; - - /** - * @var \Magento\Catalog\Api\Data\ProductExtensionFactory - */ - protected $productExtensionFactory; + private $stockRegistry; /** * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry - * @param \Magento\Catalog\Api\Data\ProductExtensionFactory $productExtensionFactory */ public function __construct( - \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry, - \Magento\Catalog\Api\Data\ProductExtensionFactory $productExtensionFactory + \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry ) { $this->stockRegistry = $stockRegistry; - $this->productExtensionFactory = $productExtensionFactory; } /** @@ -40,10 +37,6 @@ public function __construct( public function afterLoad(\Magento\Catalog\Model\Product $product) { $productExtension = $product->getExtensionAttributes(); - if ($productExtension === null) { - $productExtension = $this->productExtensionFactory->create(); - } - // stockItem := \Magento\CatalogInventory\Api\Data\StockItemInterface $productExtension->setStockItem($this->stockRegistry->getStockItem($product->getId())); $product->setExtensionAttributes($productExtension); return $product; diff --git a/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php b/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php index 15ef1d8a85eb4..a48be126d8eee 100644 --- a/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php +++ b/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php @@ -4,61 +4,24 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\CatalogInventory\Model\Plugin; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\CatalogInventory\Api\Data\StockItemInterface; -use Magento\CatalogInventory\Api\StockConfigurationInterface; -use Magento\CatalogInventory\Api\StockRegistryInterface; -use Magento\Framework\Exception\LocalizedException; -use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\Exception\CouldNotSaveException; /** - * Plugin for Magento\Catalog\Api\ProductRepositoryInterface + * Plugin is needed for backward compatibility * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @deprecated Stock data should be processed using the module API + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process */ class AroundProductRepositorySave { /** - * @var StockRegistryInterface - */ - protected $stockRegistry; - - /** - * @var StoreManagerInterface - * @deprecated - */ - protected $storeManager; - - /** - * @var StockConfigurationInterface - */ - private $stockConfiguration; - - /** - * @param StockRegistryInterface $stockRegistry - * @param StoreManagerInterface $storeManager - * @param StockConfigurationInterface $stockConfiguration - */ - public function __construct( - StockRegistryInterface $stockRegistry, - StoreManagerInterface $storeManager, - StockConfigurationInterface $stockConfiguration - ) { - $this->stockRegistry = $stockRegistry; - $this->storeManager = $storeManager; - $this->stockConfiguration = $stockConfiguration; - } - - /** - * Save stock item information from received product - * - * Pay attention that in this code we mostly work with original product object to process stock item data, - * not with received result (saved product) because it is already contains new empty stock item object. + * Plugin is needed for backward compatibility * * @param ProductRepositoryInterface $subject * @param ProductInterface $result @@ -75,93 +38,7 @@ public function afterSave( ProductInterface $product, $saveOptions = false ) { - /* @var StockItemInterface $stockItem */ - $stockItem = $this->getStockItemToBeUpdated($product); - if (null === $stockItem) { - return $result; - } - - // set fields that the customer should not care about - $stockItem->setProductId($product->getId()); - $stockItem->setWebsiteId($this->stockConfiguration->getDefaultScopeId()); - - $this->stockRegistry->updateStockItemBySku($product->getSku(), $stockItem); - // since we just saved a portion of the product, force a reload of it before returning it - return $subject->get($product->getSku(), false, $product->getStoreId(), true); - } - - /** - * Return the stock item that needs to be updated. - * If the stock item does not need to be updated, return null. - * - * @param ProductInterface $product - * @return StockItemInterface|null - * @throws LocalizedException - */ - protected function getStockItemToBeUpdated($product) - { - // from the API, all the data we care about will exist as extension attributes of the original product - $stockItem = $this->getStockItemFromProduct($product); - - if ($stockItem === null) { - $stockItem = $this->stockRegistry->getStockItem($product->getId()); - if ($stockItem->getItemId() !== null) { - // we already have stock item info, so we return null since nothing more needs to be updated - $stockItem = null; - } - } - - return $stockItem; - } - - /** - * @param ProductInterface $product - * @return StockItemInterface - * @throws LocalizedException - */ - private function getStockItemFromProduct(ProductInterface $product) - { - $stockItem = null; - $extendedAttributes = $product->getExtensionAttributes(); - if ($extendedAttributes !== null) { - $stockItem = $extendedAttributes->getStockItem(); - if ($stockItem) { - $this->validateStockItem($product, $stockItem); - } - } - - return $stockItem; - } - - /** - * @param ProductInterface $product - * @param StockItemInterface $stockItem - * @throws LocalizedException - * @return void - */ - private function validateStockItem(ProductInterface $product, StockItemInterface $stockItem) - { - $defaultScopeId = $this->stockConfiguration->getDefaultScopeId(); - $defaultStockId = $this->stockRegistry->getStock($defaultScopeId)->getStockId(); - $stockId = $stockItem->getStockId(); - if ($stockId !== null && $stockId != $defaultStockId) { - throw new LocalizedException( - __('Invalid stock id: %1. Only default stock with id %2 allowed', $stockId, $defaultStockId) - ); - } - $stockItemId = $stockItem->getItemId(); - if ($stockItemId !== null && (!is_numeric($stockItemId) || $stockItemId <= 0)) { - throw new LocalizedException( - __('Invalid stock item id: %1. Should be null or numeric value greater than 0', $stockItemId) - ); - } - - $defaultStockItemId = $this->stockRegistry->getStockItem($product->getId())->getItemId(); - if ($defaultStockItemId && $stockItemId !== null && $defaultStockItemId != $stockItemId) { - throw new LocalizedException( - __('Invalid stock item id: %1. Assigned stock item id is %2', $stockItemId, $defaultStockItemId) - ); - } + return $subject->get($product->getSku(), false, $product->getStoreId()); } } diff --git a/app/code/Magento/CatalogInventory/Model/StockItemValidator.php b/app/code/Magento/CatalogInventory/Model/StockItemValidator.php new file mode 100644 index 0000000000000..2cc4832159d55 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Model/StockItemValidator.php @@ -0,0 +1,74 @@ +stockConfiguration = $stockConfiguration; + $this->stockRegistry = $stockRegistry; + } + + /** + * Validate Stock item + * + * @param ProductInterface $product + * @param StockItemInterface $stockItem + * @throws LocalizedException + * @return void + */ + public function validate(ProductInterface $product, StockItemInterface $stockItem) + { + $defaultScopeId = $this->stockConfiguration->getDefaultScopeId(); + $defaultStockId = $this->stockRegistry->getStock($defaultScopeId)->getStockId(); + $stockId = $stockItem->getStockId(); + if ($stockId !== null && $stockId != $defaultStockId) { + throw new LocalizedException( + __('Invalid stock id: %1. Only default stock with id %2 allowed', $stockId, $defaultStockId) + ); + } + + $stockItemId = $stockItem->getItemId(); + if ($stockItemId !== null && (!is_numeric($stockItemId) || $stockItemId <= 0)) { + throw new LocalizedException( + __('Invalid stock item id: %1. Should be null or numeric value greater than 0', $stockItemId) + ); + } + + $defaultStockItemId = $this->stockRegistry->getStockItem($product->getId())->getItemId(); + if ($defaultStockItemId && $stockItemId !== null && $defaultStockItemId != $stockItemId) { + throw new LocalizedException( + __('Invalid stock item id: %1. Assigned stock item id is %2', $stockItemId, $defaultStockItemId) + ); + } + } +} diff --git a/app/code/Magento/CatalogInventory/Observer/ProcessInventoryDataObserver.php b/app/code/Magento/CatalogInventory/Observer/ProcessInventoryDataObserver.php new file mode 100644 index 0000000000000..4d4628e25efff --- /dev/null +++ b/app/code/Magento/CatalogInventory/Observer/ProcessInventoryDataObserver.php @@ -0,0 +1,132 @@ +stockRegistry = $stockRegistry; + } + + /** + * Process stock item data + * + * @param EventObserver $observer + * @return void + */ + public function execute(EventObserver $observer) + { + $product = $observer->getEvent()->getProduct(); + $this->processStockData($product); + } + + /** + * Process stock item data + * + * Synchronize stock data from different sources (stock_data, quantity_and_stock_status, StockItem) and set it to + * stock_data key + * + * @param Product $product + * @return void + */ + private function processStockData(Product $product) + { + /** @var Item $stockItem */ + $stockItem = $this->stockRegistry->getStockItem($product->getId(), $product->getStore()->getWebsiteId()); + + $quantityAndStockStatus = $product->getData('quantity_and_stock_status'); + if (is_array($quantityAndStockStatus)) { + $quantityAndStockStatus = $this->prepareQuantityAndStockStatus($stockItem, $quantityAndStockStatus); + + if ($quantityAndStockStatus) { + $this->setStockDataToProduct($product, $stockItem, $quantityAndStockStatus); + } + } + $product->unsetData('quantity_and_stock_status'); + } + + /** + * Prepare quantity_and_stock_status data + * + * Remove not changed values from quantity_and_stock_status data + * Set null value for qty if passed empty + * + * @param StockItemInterface $stockItem + * @param array $quantityAndStockStatus + * @return array + */ + private function prepareQuantityAndStockStatus(StockItemInterface $stockItem, array $quantityAndStockStatus) + { + $stockItemId = $stockItem->getItemId(); + + if (null !== $stockItemId) { + if (isset($quantityAndStockStatus['is_in_stock']) + && $stockItem->getIsInStock() == $quantityAndStockStatus['is_in_stock'] + ) { + unset($quantityAndStockStatus['is_in_stock']); + } + if (isset($quantityAndStockStatus['qty']) + && $stockItem->getQty() == $quantityAndStockStatus['qty'] + ) { + unset($quantityAndStockStatus['qty']); + } + } + + if (array_key_exists('qty', $quantityAndStockStatus) && $quantityAndStockStatus['qty'] === '') { + $quantityAndStockStatus['qty'] = null; + } + return $quantityAndStockStatus; + } + + /** + * Set stock data to product + * + * First of all we take stock_data data, replace it from quantity_and_stock_status data (if was changed) and finally + * replace it with data from Stock Item object (only if Stock Item was changed) + * + * @param Product $product + * @param Item $stockItem + * @param array $quantityAndStockStatus + * @return void + */ + private function setStockDataToProduct(Product $product, Item $stockItem, array $quantityAndStockStatus) + { + $stockData = array_replace((array)$product->getData('stock_data'), $quantityAndStockStatus); + if ($stockItem->hasDataChanges()) { + $stockData = array_replace($stockData, $stockItem->getData()); + } + $product->setData('stock_data', $stockData); + } +} diff --git a/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php b/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php index 57c3e7053cd12..5f1c4f4350b12 100644 --- a/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php +++ b/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php @@ -3,43 +3,46 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\CatalogInventory\Observer; +use Magento\Catalog\Model\Product; +use Magento\CatalogInventory\Model\Stock\Item; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Event\ObserverInterface; use Magento\CatalogInventory\Api\StockConfigurationInterface; -use Magento\CatalogInventory\Api\StockIndexInterface; -use Magento\CatalogInventory\Api\StockItemRepositoryInterface; use Magento\CatalogInventory\Api\StockRegistryInterface; +use Magento\CatalogInventory\Model\StockItemValidator; use Magento\Framework\Event\Observer as EventObserver; +/** + * Saves stock data from a product to the Stock Item + * + * @deprecated Stock data should be processed using the module API + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ class SaveInventoryDataObserver implements ObserverInterface { - /** - * @var StockIndexInterface - * @deprecated - */ - protected $stockIndex; - /** * @var StockConfigurationInterface */ - protected $stockConfiguration; + private $stockConfiguration; /** * @var StockRegistryInterface */ - protected $stockRegistry; + private $stockRegistry; /** - * @var StockItemRepositoryInterface + * @var StockItemValidator */ - protected $stockItemRepository; + private $stockItemValidator; /** * @var array */ - protected $paramListToCheck = [ + private $paramListToCheck = [ 'use_config_min_qty' => [ 'item' => 'stock_data/min_qty', 'config' => 'stock_data/use_config_min_qty', @@ -71,73 +74,86 @@ class SaveInventoryDataObserver implements ObserverInterface ]; /** - * @param StockIndexInterface $stockIndex * @param StockConfigurationInterface $stockConfiguration * @param StockRegistryInterface $stockRegistry - * @param StockItemRepositoryInterface $stockItemRepository + * @param StockItemValidator $stockItemValidator */ public function __construct( - StockIndexInterface $stockIndex, StockConfigurationInterface $stockConfiguration, StockRegistryInterface $stockRegistry, - StockItemRepositoryInterface $stockItemRepository + StockItemValidator $stockItemValidator = null ) { - $this->stockIndex = $stockIndex; $this->stockConfiguration = $stockConfiguration; $this->stockRegistry = $stockRegistry; - $this->stockItemRepository = $stockItemRepository; + $this->stockItemValidator = $stockItemValidator ?: ObjectManager::getInstance()->get(StockItemValidator::class); } /** - * Saving product inventory data. Product qty calculated dynamically. + * Saving product inventory data + * + * Takes data from the stock_data property of a product and sets it to Stock Item. + * Validates and saves Stock Item object. * * @param EventObserver $observer - * @return $this + * @return void */ public function execute(EventObserver $observer) { $product = $observer->getEvent()->getProduct(); - if ($product->getStockData() === null) { - return $this; + $stockItem = $this->getStockItemToBeUpdated($product); + + if ($product->getStockData() !== null) { + $stockData = $this->getStockData($product); + $stockItem->addData($stockData); } + $this->stockItemValidator->validate($product, $stockItem); + $this->stockRegistry->updateStockItemBySku($product->getSku(), $stockItem); + } - $this->saveStockItemData($product); - return $this; + /** + * Return the stock item that needs to be updated + * + * @param Product $product + * @return Item + */ + private function getStockItemToBeUpdated(Product $product) + { + $extendedAttributes = $product->getExtensionAttributes(); + $stockItem = $extendedAttributes->getStockItem(); + + if ($stockItem === null) { + $stockItem = $this->stockRegistry->getStockItem($product->getId()); + } + return $stockItem; } /** - * Prepare stock item data for save + * Get stock data * - * @param \Magento\Catalog\Model\Product $product - * @return $this + * @param Product $product + * @return array */ - protected function saveStockItemData($product) + private function getStockData(Product $product) { - $stockItemData = $product->getStockData(); - $stockItemData['product_id'] = $product->getId(); + $stockData = $product->getStockData(); + $stockData['product_id'] = $product->getId(); - if (!isset($stockItemData['website_id'])) { - $stockItemData['website_id'] = $this->stockConfiguration->getDefaultScopeId(); + if (!isset($stockData['website_id'])) { + $stockData['website_id'] = $this->stockConfiguration->getDefaultScopeId(); } - $stockItemData['stock_id'] = $this->stockRegistry->getStock($stockItemData['website_id'])->getStockId(); + $stockData['stock_id'] = $this->stockRegistry->getStock($stockData['website_id'])->getStockId(); foreach ($this->paramListToCheck as $dataKey => $configPath) { if (null !== $product->getData($configPath['item']) && null === $product->getData($configPath['config'])) { - $stockItemData[$dataKey] = false; + $stockData[$dataKey] = false; } } $originalQty = $product->getData('stock_data/original_inventory_qty'); if (strlen($originalQty) > 0) { - $stockItemData['qty_correction'] = (isset($stockItemData['qty']) ? $stockItemData['qty'] : 0) + $stockData['qty_correction'] = (isset($stockData['qty']) ? $stockData['qty'] : 0) - $originalQty; } - - // todo resolve issue with builder and identity field name - $stockItem = $this->stockRegistry->getStockItem($stockItemData['product_id'], $stockItemData['website_id']); - - $stockItem->addData($stockItemData); - $this->stockItemRepository->save($stockItem); - return $this; + return $stockData; } } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php index 375ed5937c272..408802d80bd57 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php @@ -27,7 +27,7 @@ class AfterProductLoadTest extends \PHPUnit_Framework_TestCase protected $productExtensionFactoryMock; /** - * @var \Magento\Catalog\Api\Data\ProductExtension|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Catalog\Api\Data\ProductExtensionInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $productExtensionMock; @@ -53,7 +53,7 @@ protected function setUp() ->with($productId) ->willReturn($stockItemMock); - $this->productExtensionMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtension::class) + $this->productExtensionMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtensionInterface::class) ->setMethods(['setStockItem']) ->getMock(); $this->productExtensionMock->expects($this->once()) @@ -75,23 +75,6 @@ protected function setUp() public function testAfterLoad() { - // test when extension attributes are not (yet) present in the product - $this->productMock->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn(null); - $this->productExtensionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($this->productExtensionMock); - - $this->assertEquals( - $this->productMock, - $this->plugin->afterLoad($this->productMock) - ); - } - - public function testAfterLoadWithExistingExtensionAttributes() - { - // test when extension attributes already exist $this->productMock->expects($this->once()) ->method('getExtensionAttributes') ->willReturn($this->productExtensionMock); diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AroundProductRepositorySaveTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AroundProductRepositorySaveTest.php deleted file mode 100644 index 86a4ba3e25fe0..0000000000000 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AroundProductRepositorySaveTest.php +++ /dev/null @@ -1,350 +0,0 @@ -stockRegistry = $this->getMockBuilder(StockRegistryInterface::class) - ->setMethods(['getStockItem', 'updateStockItemBySku']) - ->getMockForAbstractClass(); - $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class) - ->getMockForAbstractClass(); - $this->stockConfiguration = $this->getMockBuilder(StockConfigurationInterface::class) - ->setMethods(['getDefaultScopeId']) - ->getMockForAbstractClass(); - - $this->plugin = new AroundProductRepositorySave( - $this->stockRegistry, - $this->storeManager, - $this->stockConfiguration - ); - - $this->savedProduct = $this->getMockBuilder(ProductInterface::class) - ->setMethods(['getExtensionAttributes', 'getStoreId']) - ->getMockForAbstractClass(); - - $this->productRepository = $this->getMockBuilder(ProductRepositoryInterface::class) - ->setMethods(['get']) - ->getMockForAbstractClass(); - $this->product = $this->getMockBuilder(ProductInterface::class) - ->setMethods(['getExtensionAttributes', 'getStoreId']) - ->getMockForAbstractClass(); - $this->productExtension = $this->getMockForAbstractClass( - ProductExtensionInterface::class, - [], - '', - false, - false, - true, - ['getStockItem'] - ); - $this->stockItem = $this->getMockBuilder(StockItemInterface::class) - ->setMethods(['setWebsiteId', 'getWebsiteId', 'getStockId']) - ->getMockForAbstractClass(); - $this->defaultStock = $this->getMockBuilder(StockInterface::class) - ->setMethods(['getStockId']) - ->getMockForAbstractClass(); - } - - public function testAfterSaveWhenProductHasNoStockItemNeedingToBeUpdated() - { - // pretend we have no extension attributes at all - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn(null); - $this->productExtension->expects($this->never())->method('getStockItem'); - - // pretend that the product already has existing stock item information - $this->stockRegistry->expects($this->once())->method('getStockItem')->willReturn($this->stockItem); - $this->stockItem->expects($this->once())->method('getItemId')->willReturn(1); - $this->stockItem->expects($this->never())->method('setProductId'); - $this->stockItem->expects($this->never())->method('setWebsiteId'); - - // expect that there are no changes to the existing stock item information - $result = $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product); - $this->assertEquals( - $this->savedProduct, - $result - ); - } - - public function testAfterSaveWhenProductHasNoPersistentStockItemInfo() - { - // pretend we do have extension attributes, but none for 'stock_item' - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn($this->productExtension); - $this->productExtension->expects($this->once()) - ->method('getStockItem') - ->willReturn(null); - - $this->stockConfiguration->expects($this->once())->method('getDefaultScopeId')->willReturn(1); - $this->stockRegistry->expects($this->once())->method('getStockItem')->willReturn($this->stockItem); - $this->stockRegistry->expects($this->once())->method('updateStockItemBySku'); - - $this->stockItem->expects($this->once())->method('getItemId')->willReturn(null); - $this->stockItem->expects($this->once())->method('setProductId'); - $this->stockItem->expects($this->once())->method('setWebsiteId'); - $this->product->expects(($this->atLeastOnce()))->method('getStoreId')->willReturn(20); - - $newProductMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->disableOriginalConstructor()->getMock(); - $this->productRepository->expects($this->once())->method('get')->willReturn($newProductMock); - - $this->assertEquals( - $newProductMock, - $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product) - ); - } - - public function testAfterSave() - { - $productId = 5494; - $storeId = 2; - $sku = 'my product that needs saving'; - $defaultScopeId = 100; - $this->stockConfiguration->expects($this->exactly(2)) - ->method('getDefaultScopeId') - ->willReturn($defaultScopeId); - $this->stockRegistry->expects($this->once()) - ->method('getStock') - ->with($defaultScopeId) - ->willReturn($this->defaultStock); - - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn($this->productExtension); - $this->productExtension->expects($this->once()) - ->method('getStockItem') - ->willReturn($this->stockItem); - - $storedStockItem = $this->getMockBuilder(StockItemInterface::class) - ->setMethods(['getItemId']) - ->getMockForAbstractClass(); - $storedStockItem->expects($this->once()) - ->method('getItemId') - ->willReturn(500); - $this->stockRegistry->expects($this->once()) - ->method('getStockItem') - ->willReturn($storedStockItem); - - $this->product->expects(($this->exactly(2)))->method('getId')->willReturn($productId); - $this->product->expects(($this->atLeastOnce()))->method('getStoreId')->willReturn($storeId); - $this->product->expects($this->atLeastOnce())->method('getSku')->willReturn($sku); - - $this->stockItem->expects($this->once())->method('setProductId')->with($productId); - $this->stockItem->expects($this->once())->method('setWebsiteId')->with($defaultScopeId); - - $this->stockRegistry->expects($this->once()) - ->method('updateStockItemBySku') - ->with($sku, $this->stockItem); - - $newProductMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->disableOriginalConstructor()->getMock(); - $this->productRepository->expects($this->once()) - ->method('get') - ->with($sku, false, $storeId, true) - ->willReturn($newProductMock); - - $this->assertEquals( - $newProductMock, - $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product) - ); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Invalid stock id: 100500. Only default stock with id 50 allowed - */ - public function testAfterSaveWithInvalidStockId() - { - $stockId = 100500; - $defaultScopeId = 100; - $defaultStockId = 50; - - $this->stockItem->expects($this->once()) - ->method('getStockId') - ->willReturn($stockId); - $this->stockRegistry->expects($this->once()) - ->method('getStock') - ->with($defaultScopeId) - ->willReturn($this->defaultStock); - $this->stockConfiguration->expects($this->once()) - ->method('getDefaultScopeId') - ->willReturn($defaultScopeId); - $this->defaultStock->expects($this->once()) - ->method('getStockId') - ->willReturn($defaultStockId); - - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn($this->productExtension); - $this->productExtension->expects($this->once()) - ->method('getStockItem') - ->willReturn($this->stockItem); - - $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Invalid stock item id: 0. Should be null or numeric value greater than 0 - */ - public function testAfterSaveWithInvalidStockItemId() - { - $stockId = 80; - $stockItemId = 0; - $defaultScopeId = 100; - $defaultStockId = 80; - - $this->stockItem->expects($this->once()) - ->method('getStockId') - ->willReturn($stockId); - $this->stockRegistry->expects($this->once()) - ->method('getStock') - ->with($defaultScopeId) - ->willReturn($this->defaultStock); - $this->stockConfiguration->expects($this->once()) - ->method('getDefaultScopeId') - ->willReturn($defaultScopeId); - $this->defaultStock->expects($this->once()) - ->method('getStockId') - ->willReturn($defaultStockId); - - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn($this->productExtension); - $this->productExtension->expects($this->once()) - ->method('getStockItem') - ->willReturn($this->stockItem); - - $this->stockItem->expects($this->once()) - ->method('getItemId') - ->willReturn($stockItemId); - - $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Invalid stock item id: 35. Assigned stock item id is 40 - */ - public function testAfterSaveWithNotAssignedStockItemId() - { - $stockId = 80; - $stockItemId = 35; - $defaultScopeId = 100; - $defaultStockId = 80; - $storedStockitemId = 40; - - $this->stockItem->expects($this->once()) - ->method('getStockId') - ->willReturn($stockId); - $this->stockRegistry->expects($this->once()) - ->method('getStock') - ->with($defaultScopeId) - ->willReturn($this->defaultStock); - $this->stockConfiguration->expects($this->once()) - ->method('getDefaultScopeId') - ->willReturn($defaultScopeId); - $this->defaultStock->expects($this->once()) - ->method('getStockId') - ->willReturn($defaultStockId); - - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn($this->productExtension); - $this->productExtension->expects($this->once()) - ->method('getStockItem') - ->willReturn($this->stockItem); - - $this->stockItem->expects($this->once()) - ->method('getItemId') - ->willReturn($stockItemId); - - $storedStockItem = $this->getMockBuilder(StockItemInterface::class) - ->setMethods(['getItemId']) - ->getMockForAbstractClass(); - $storedStockItem->expects($this->once()) - ->method('getItemId') - ->willReturn($storedStockitemId); - $this->stockRegistry->expects($this->once()) - ->method('getStockItem') - ->willReturn($storedStockItem); - - $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product); - } -} diff --git a/app/code/Magento/CatalogInventory/etc/events.xml b/app/code/Magento/CatalogInventory/etc/events.xml index 97af0f934ad9f..3b5f2483ec57e 100644 --- a/app/code/Magento/CatalogInventory/etc/events.xml +++ b/app/code/Magento/CatalogInventory/etc/events.xml @@ -33,6 +33,9 @@ + + + diff --git a/app/code/Magento/CatalogInventory/etc/extension_attributes.xml b/app/code/Magento/CatalogInventory/etc/extension_attributes.xml index 2e30788604908..11bebb96ce313 100644 --- a/app/code/Magento/CatalogInventory/etc/extension_attributes.xml +++ b/app/code/Magento/CatalogInventory/etc/extension_attributes.xml @@ -7,6 +7,12 @@ --> + diff --git a/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php b/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php index b8770482f0324..0bd28f0f78d96 100644 --- a/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php +++ b/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php @@ -8,6 +8,7 @@ use Magento\Config\App\Config\Type\System; use Magento\Config\Model\PreparedValueFactory; use Magento\Framework\App\Config\ConfigPathResolver; +use Magento\Framework\App\Config\Value; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Config\File\ConfigFilePool; use Magento\Framework\Exception\CouldNotSaveException; @@ -79,19 +80,27 @@ public function process($path, $value, $scope, $scopeCode) $configPath = $this->configPathResolver->resolve($path, $scope, $scopeCode, System::CONFIG_TYPE); $backendModel = $this->preparedValueFactory->create($path, $value, $scope, $scopeCode); - /** - * Temporary solution until Magento introduce unified interface - * for storing system configuration into database and configuration files. - */ - $backendModel->validateBeforeSave(); - $backendModel->beforeSave(); + if ($backendModel instanceof Value) { + /** + * Temporary solution until Magento introduce unified interface + * for storing system configuration into database and configuration files. + */ + $backendModel->validateBeforeSave(); + $backendModel->beforeSave(); - $this->deploymentConfigWriter->saveConfig( - [ConfigFilePool::APP_ENV => $this->arrayManager->set($configPath, [], $backendModel->getValue())], - false - ); + $value = $backendModel->getValue(); - $backendModel->afterSave(); + $backendModel->afterSave(); + + /** + * Because FS does not support transactions, + * we'll write value just after all validations are triggered. + */ + $this->deploymentConfigWriter->saveConfig( + [ConfigFilePool::APP_ENV => $this->arrayManager->set($configPath, [], $value)], + false + ); + } } catch (\Exception $exception) { throw new CouldNotSaveException(__('%1', $exception->getMessage()), $exception); } diff --git a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php index e37214411ff51..62028eb789230 100644 --- a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php +++ b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php @@ -206,10 +206,10 @@ public function testCustomException() ->willReturn($this->valueMock); $this->arrayManagerMock->expects($this->never()) ->method('set'); - $this->valueMock->expects($this->never()) + $this->valueMock->expects($this->once()) ->method('getValue'); $this->valueMock->expects($this->once()) - ->method('validateBeforeSave') + ->method('afterSave') ->willThrowException(new \Exception('Invalid values')); $this->deploymentConfigWriterMock->expects($this->never()) ->method('saveConfig'); diff --git a/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php b/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php index ad7809ecee2ee..4c931a156a5f7 100644 --- a/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php +++ b/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php @@ -7,9 +7,12 @@ use Magento\Framework\App\Config\ConfigSourceInterface; use Magento\Framework\App\DeploymentConfig; +use Magento\Store\Model\Group; use Magento\Store\Model\ResourceModel\Website\CollectionFactory as WebsiteCollectionFactory; use Magento\Store\Model\ResourceModel\Group\CollectionFactory as GroupCollectionFactory; use Magento\Store\Model\ResourceModel\Store\CollectionFactory as StoreCollectionFactory; +use Magento\Store\Model\Store; +use Magento\Store\Model\Website; use Magento\Store\Model\WebsiteFactory; use Magento\Store\Model\GroupFactory; use Magento\Store\Model\StoreFactory; @@ -100,13 +103,13 @@ public function get($path = '') if ($this->canUseDatabase()) { switch ($scopePool) { case 'websites': - $data = $this->getWebsitesData($scopeCode); + $data['websites'] = $this->getWebsitesData($scopeCode); break; case 'groups': - $data = $this->getGroupsData($scopeCode); + $data['groups'] = $this->getGroupsData($scopeCode); break; case 'stores': - $data = $this->getStoresData($scopeCode); + $data['stores'] = $this->getStoresData($scopeCode); break; default: $data = [ @@ -127,14 +130,15 @@ public function get($path = '') */ private function getWebsitesData($code = null) { - if ($code) { + if ($code !== null) { $website = $this->websiteFactory->create(); $website->load($code); - $data = $website->getData(); + $data[$code] = $website->getData(); } else { $collection = $this->websiteCollectionFactory->create(); $collection->setLoadDefault(true); $data = []; + /** @var Website $website */ foreach ($collection as $website) { $data[$website->getCode()] = $website->getData(); } @@ -148,14 +152,15 @@ private function getWebsitesData($code = null) */ private function getGroupsData($id = null) { - if ($id) { + if ($id !== null) { $group = $this->groupFactory->create(); $group->load($id); - $data = $group->getData(); + $data[$id] = $group->getData(); } else { $collection = $this->groupCollectionFactory->create(); $collection->setLoadDefault(true); $data = []; + /** @var Group $group */ foreach ($collection as $group) { $data[$group->getId()] = $group->getData(); } @@ -169,14 +174,21 @@ private function getGroupsData($id = null) */ private function getStoresData($code = null) { - if ($code) { + if ($code !== null) { $store = $this->storeFactory->create(); - $store->load($code, 'code'); - $data = $store->getData(); + + if (is_numeric($code)) { + $store->load($code); + } else { + $store->load($code, 'code'); + } + + $data[$code] = $store->getData(); } else { $collection = $this->storeCollectionFactory->create(); $collection->setLoadDefault(true); $data = []; + /** @var Store $store */ foreach ($collection as $store) { $data[$store->getCode()] = $store->getData(); } diff --git a/app/code/Magento/Store/App/Config/Type/Scopes.php b/app/code/Magento/Store/App/Config/Type/Scopes.php index d9dc0278e8627..b1f266c8b0cfe 100644 --- a/app/code/Magento/Store/App/Config/Type/Scopes.php +++ b/app/code/Magento/Store/App/Config/Type/Scopes.php @@ -34,6 +34,7 @@ public function __construct( ConfigSourceInterface $source ) { $this->source = $source; + $this->data = new DataObject(); } /** @@ -41,8 +42,10 @@ public function __construct( */ public function get($path = '') { - if (!$this->data) { - $this->data = new DataObject($this->source->get()); + $patchChunks = explode("/", $path); + + if (!$this->data->getData($path) || count($patchChunks) == 1) { + $this->data->addData($this->source->get($path)); } return $this->data->getData($path); @@ -55,6 +58,6 @@ public function get($path = '') */ public function clean() { - $this->data = null; + $this->data = new DataObject(); } } diff --git a/app/code/Magento/Store/Model/Config/Processor/Fallback.php b/app/code/Magento/Store/Model/Config/Processor/Fallback.php index 2578ea79a41db..91926cd23f9cf 100644 --- a/app/code/Magento/Store/Model/Config/Processor/Fallback.php +++ b/app/code/Magento/Store/Model/Config/Processor/Fallback.php @@ -6,7 +6,16 @@ namespace Magento\Store\Model\Config\Processor; use Magento\Framework\App\Config\Spi\PostProcessorInterface; +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\ResourceConnection; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\Data\WebsiteInterface; use Magento\Store\App\Config\Type\Scopes; +use Magento\Store\Model\ResourceModel\Store; +use Magento\Store\Model\ResourceModel\Store\AllStoresCollectionFactory; +use Magento\Store\Model\ResourceModel\Website; +use Magento\Store\Model\ResourceModel\Website\AllWebsitesCollection; +use Magento\Store\Model\ResourceModel\Website\AllWebsitesCollectionFactory; /** * Fallback through different scopes and merge them @@ -18,13 +27,57 @@ class Fallback implements PostProcessorInterface */ private $scopes; + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var array + */ + private $storeData = []; + + /** + * @var array + */ + private $websiteData = []; + + /** + * @var Store + */ + private $storeResource; + + /** + * @var Website + */ + private $websiteResource; + + /** + * @var DeploymentConfig + */ + private $deploymentConfig; + /** * Fallback constructor. + * * @param Scopes $scopes + * @param ResourceConnection $resourceConnection + * @param Store $storeResource + * @param Website $websiteResource + * @param DeploymentConfig $deploymentConfig */ - public function __construct(Scopes $scopes) - { + public function __construct( + Scopes $scopes, + ResourceConnection $resourceConnection, + Store $storeResource, + Website $websiteResource, + DeploymentConfig $deploymentConfig + ) { $this->scopes = $scopes; + $this->resourceConnection = $resourceConnection; + $this->storeResource = $storeResource; + $this->websiteResource = $websiteResource; + $this->deploymentConfig = $deploymentConfig; } /** @@ -32,6 +85,14 @@ public function __construct(Scopes $scopes) */ public function process(array $data) { + if ($this->deploymentConfig->isDbAvailable()) {//read only from db + $this->storeData = $this->storeResource->readAllStores(); + $this->websiteData = $this->websiteResource->readAllWebsites(); + } else { + $this->storeData = $this->scopes->get('stores'); + $this->websiteData = $this->scopes->get('websites'); + } + $defaultConfig = isset($data['default']) ? $data['default'] : []; $result = [ 'default' => $defaultConfig, @@ -55,12 +116,14 @@ public function process(array $data) * @param array $websitesConfig * @return array */ - private function prepareWebsitesConfig(array $defaultConfig, array $websitesConfig) - { + private function prepareWebsitesConfig( + array $defaultConfig, + array $websitesConfig + ) { $result = []; - foreach ((array)$this->scopes->get('websites') as $websiteData) { - $code = $websiteData['code']; - $id = $websiteData['website_id']; + foreach ((array)$this->websiteData as $website) { + $code = $website['code']; + $id = $website['website_id']; $websiteConfig = isset($websitesConfig[$code]) ? $websitesConfig[$code] : []; $result[$code] = array_replace_recursive($defaultConfig, $websiteConfig); $result[$id] = $result[$code]; @@ -76,15 +139,19 @@ private function prepareWebsitesConfig(array $defaultConfig, array $websitesConf * @param array $storesConfig * @return array */ - private function prepareStoresConfig(array $defaultConfig, array $websitesConfig, array $storesConfig) - { + private function prepareStoresConfig( + array $defaultConfig, + array $websitesConfig, + array $storesConfig + ) { $result = []; - foreach ((array)$this->scopes->get('stores') as $storeData) { - $code = $storeData['code']; - $id = $storeData['store_id']; + + foreach ((array)$this->storeData as $store) { + $code = $store['code']; + $id = $store['store_id']; $websiteConfig = []; - if (isset($storeData['website_id'])) { - $websiteConfig = $this->getWebsiteConfig($websitesConfig, $storeData['website_id']); + if (isset($store['website_id'])) { + $websiteConfig = $this->getWebsiteConfig($websitesConfig, $store['website_id']); } $storeConfig = isset($storesConfig[$code]) ? $storesConfig[$code] : []; $result[$code] = array_replace_recursive($defaultConfig, $websiteConfig, $storeConfig); @@ -94,17 +161,17 @@ private function prepareStoresConfig(array $defaultConfig, array $websitesConfig } /** - * Retrieve Website Config + * Find information about website by its ID. * - * @param array $websites + * @param array $websites Has next format: (website_code => [website_data]) * @param int $id * @return array */ private function getWebsiteConfig(array $websites, $id) { - foreach ($this->scopes->get('websites') as $websiteData) { - if ($websiteData['website_id'] == $id) { - $code = $websiteData['code']; + foreach ((array)$this->websiteData as $website) { + if ($website['website_id'] == $id) { + $code = $website['code']; return isset($websites[$code]) ? $websites[$code] : []; } } diff --git a/app/code/Magento/Store/Model/GroupRepository.php b/app/code/Magento/Store/Model/GroupRepository.php index 80cc6f12c4de8..da1b82c3bf688 100644 --- a/app/code/Magento/Store/Model/GroupRepository.php +++ b/app/code/Magento/Store/Model/GroupRepository.php @@ -62,18 +62,8 @@ public function get($id) return $this->entities[$id]; } - $groupData = []; - $groups = $this->getAppConfig()->get('scopes', 'groups', []); - if ($groups) { - foreach ($groups as $data) { - if (isset($data['group_id']) && $data['group_id'] == $id) { - $groupData = $data; - break; - } - } - } $group = $this->groupFactory->create([ - 'data' => $groupData + 'data' => $this->getAppConfig()->get('scopes', "groups/$id", []) ]); if (null === $group->getId()) { diff --git a/app/code/Magento/Store/Model/ResourceModel/Store.php b/app/code/Magento/Store/Model/ResourceModel/Store.php index 5e16fbd68cedc..4446472a71176 100644 --- a/app/code/Magento/Store/Model/ResourceModel/Store.php +++ b/app/code/Magento/Store/Model/ResourceModel/Store.php @@ -157,6 +157,20 @@ protected function _changeGroup(\Magento\Framework\Model\AbstractModel $model) return $this; } + /** + * Read information about all stores + * + * @return array + */ + public function readAllStores() + { + $select = $this->getConnection() + ->select() + ->from($this->getTable('store')); + + return $this->getConnection()->fetchAll($select); + } + /** * Retrieve select object for load object data * diff --git a/app/code/Magento/Store/Model/ResourceModel/Website.php b/app/code/Magento/Store/Model/ResourceModel/Website.php index e8aa73728dc27..9cd52ca2eb98b 100644 --- a/app/code/Magento/Store/Model/ResourceModel/Website.php +++ b/app/code/Magento/Store/Model/ResourceModel/Website.php @@ -36,6 +36,28 @@ protected function _initUniqueFields() return $this; } + /** + * Read all information about websites. + * + * Convert information to next format: + * [website_code => [website_data (website_id, code, name, etc...)]] + * + * @return array + */ + public function readAllWebsites() + { + $websites = []; + $select = $this->getConnection() + ->select() + ->from($this->getTable('store_website')); + + foreach($this->getConnection()->fetchAll($select) as $websiteData) { + $websites[$websiteData['code']] = $websiteData; + } + + return $websites; + } + /** * Validate website code before object save * diff --git a/app/code/Magento/Store/Model/StoreRepository.php b/app/code/Magento/Store/Model/StoreRepository.php index 2ed8860615f8b..282dfb5b67081 100644 --- a/app/code/Magento/Store/Model/StoreRepository.php +++ b/app/code/Magento/Store/Model/StoreRepository.php @@ -100,14 +100,7 @@ public function getById($id) return $this->entitiesById[$id]; } - $storeData = []; - $stores = $this->getAppConfig()->get('scopes', "stores", []); - foreach ($stores as $data) { - if (isset($data['store_id']) && $data['store_id'] == $id) { - $storeData = $data; - break; - } - } + $storeData = $this->getAppConfig()->get('scopes', "stores/$id", []); $store = $this->storeFactory->create([ 'data' => $storeData ]); diff --git a/app/code/Magento/Store/Model/WebsiteRepository.php b/app/code/Magento/Store/Model/WebsiteRepository.php index 14bbe20b30d6a..3b73ba4019a01 100644 --- a/app/code/Magento/Store/Model/WebsiteRepository.php +++ b/app/code/Magento/Store/Model/WebsiteRepository.php @@ -92,14 +92,8 @@ public function getById($id) if (isset($this->entitiesById[$id])) { return $this->entitiesById[$id]; } - $websiteData = []; - $websites = $this->getAppConfig()->get('scopes', 'websites', []); - foreach ($websites as $data) { - if (isset($data['website_id']) && $data['website_id'] == $id) { - $websiteData = $data; - break; - } - } + + $websiteData = $this->getAppConfig()->get('scopes', "websites/$id", []); $website = $this->factory->create([ 'data' => $websiteData ]); @@ -185,7 +179,7 @@ private function getAppConfig() */ private function initDefaultWebsite() { - $websites = (array)$this->getAppConfig()->get('scopes', 'websites', []); + $websites = (array) $this->getAppConfig()->get('scopes', 'websites', []); foreach ($websites as $data) { if (isset($data['is_default']) && $data['is_default'] == 1) { if ($this->default) { diff --git a/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php b/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php index 74d7a3e8b0eb5..1617161fff361 100644 --- a/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php +++ b/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php @@ -104,25 +104,22 @@ class RuntimeConfigSourceTest extends \PHPUnit_Framework_TestCase public function setUp() { $this->data = [ - 'group' => [ - 'code' => 'myGroup', - 'data' => [ + 'groups' => [ + '1' => [ 'name' => 'My Group', - 'group_id' => $this->data['group']['code'] - ] + 'group_id' => 1 + ], ], - 'website' => [ - 'code' => 'myWebsite', - 'data' => [ - 'name' => 'My Website', - 'website_code' => $this->data['website']['code'] + 'stores' => [ + 'myStore' => [ + 'name' => 'My Store', + 'code' => 'myStore' ] ], - 'store' => [ - 'code' => 'myStore', - 'data' => [ - 'name' => 'My Store', - 'store_code' => $this->data['store']['code'] + 'websites' => [ + 'myWebsite' => [ + 'name' => 'My Website', + 'code' => 'myWebsite' ] ], ]; @@ -209,26 +206,16 @@ private function getExpectedResult($path) { switch ($this->getScope($path)) { case 'websites': - $result = $this->data['website']['data']; + $result = ['websites' => $this->data['websites']]; break; case 'groups': - $result = $this->data['group']['data']; + $result = ['groups' => $this->data['groups']]; break; case 'stores': - $result = $this->data['store']['data']; + $result = ['stores' => $this->data['stores']]; break; default: - $result = [ - 'websites' => [ - $this->data['website']['code'] => $this->data['website']['data'] - ], - 'groups' => [ - $this->data['group']['code'] => $this->data['group']['data'] - ], - 'stores' => [ - $this->data['store']['code'] => $this->data['store']['data'] - ], - ]; + $result = $this->data; break; } return $result; @@ -244,7 +231,7 @@ private function prepareStores($path) ->willReturn($this->store); $this->store->expects($this->once()) ->method('load') - ->with($this->data['store']['code'], 'code') + ->with('myStore', 'code') ->willReturnSelf(); } else { $this->storeCollectionFactory->expects($this->once()) @@ -259,11 +246,11 @@ private function prepareStores($path) ->willReturn(new \ArrayIterator([$this->store])); $this->store->expects($this->once()) ->method('getCode') - ->willReturn($this->data['store']['code']); + ->willReturn('myStore'); } $this->store->expects($this->once()) ->method('getData') - ->willReturn($this->data['store']['data']); + ->willReturn($this->data['stores']['myStore']); } } @@ -277,7 +264,7 @@ private function prepareGroups($path) ->willReturn($this->group); $this->group->expects($this->once()) ->method('load') - ->with($this->data['group']['code']) + ->with($this->data['groups']['1']['group_id']) ->willReturnSelf(); } else { $this->groupCollectionFactory->expects($this->once()) @@ -292,11 +279,11 @@ private function prepareGroups($path) ->willReturn(new \ArrayIterator([$this->group])); $this->group->expects($this->once()) ->method('getId') - ->willReturn($this->data['group']['code']); + ->willReturn($this->data['groups']['1']['group_id']); } $this->group->expects($this->once()) ->method('getData') - ->willReturn($this->data['group']['data']); + ->willReturn($this->data['groups']['1']); } } @@ -310,7 +297,7 @@ private function prepareWebsites($path) ->willReturn($this->website); $this->website->expects($this->once()) ->method('load') - ->with($this->data['website']['code']) + ->with($this->data['websites']['myWebsite']['code']) ->willReturnSelf(); } else { $this->websiteCollectionFactory->expects($this->once()) @@ -325,11 +312,11 @@ private function prepareWebsites($path) ->willReturn(new \ArrayIterator([$this->website])); $this->website->expects($this->once()) ->method('getCode') - ->willReturn($this->data['website']['code']); + ->willReturn('myWebsite'); } $this->website->expects($this->once()) ->method('getData') - ->willReturn($this->data['website']['data']); + ->willReturn($this->data['websites']['myWebsite']); } } @@ -350,7 +337,7 @@ public function getDataProvider() { return [ ['websites/myWebsite'], - ['groups/myGroup'], + ['groups/1'], ['stores/myStore'], ['default'] ]; diff --git a/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php b/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php index a041fffbbb110..fc826daf19a61 100644 --- a/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php +++ b/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php @@ -22,6 +22,11 @@ class Save extends Action */ protected $fileProcessor; + /** + * Authorization level + */ + const ADMIN_RESOURCE = 'Magento_Theme::theme'; + /** * @param Context $context * @param FileProcessor $fileProcessor diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php index d7f7fca3d674c..f13f88148e314 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php @@ -19,6 +19,7 @@ use Magento\Catalog\Model\Product\Visibility; use Magento\Framework\ObjectManagerInterface; use Magento\Store\Api\StoreRepositoryInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Entity; use Magento\TestFramework\Helper\Bootstrap; @@ -112,6 +113,10 @@ public function testMultipleStores() self::assertNotEquals($store->getId(), $bundle->getStoreId()); + /** @var StoreManagerInterface $storeManager */ + $storeManager = $this->objectManager->get(StoreManagerInterface::class); + $storeManager->setCurrentStore($store->getId()); + $bundle->setStoreId($store->getId()) ->setCopyFromView(true); $updatedBundle = $productRepository->save($bundle); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Attributes/ListingTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Attributes/ListingTest.php new file mode 100644 index 0000000000000..e4821afcd24d2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Attributes/ListingTest.php @@ -0,0 +1,53 @@ +request = $objectManager->get(\Magento\Framework\App\RequestInterface::class); + + /** Default Attribute Set Id is equal 4 */ + $this->request->setParams(['template_id' => 4]); + + $this->dataProvider = $objectManager->create( + \Magento\Catalog\Ui\DataProvider\Product\Attributes\Listing::class, + [ + 'name' => 'product_attributes_grid_data_source', + 'primaryFieldName' => 'attribute_id', + 'requestFieldName' => 'id', + 'request' => $this->request + ] + ); + } + + public function testGetDataSortedAsc() + { + $this->dataProvider->addOrder('attribute_code', 'asc'); + $data = $this->dataProvider->getData(); + $this->assertEquals(2, $data['totalRecords']); + $this->assertEquals('color', $data['items'][0]['attribute_code']); + $this->assertEquals('manufacturer', $data['items'][1]['attribute_code']); + } + + public function testGetDataSortedDesc() + { + $this->dataProvider->addOrder('attribute_code', 'desc'); + $data = $this->dataProvider->getData(); + $this->assertEquals(2, $data['totalRecords']); + $this->assertEquals('manufacturer', $data['items'][0]['attribute_code']); + $this->assertEquals('color', $data['items'][1]['attribute_code']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/ByStockItemRepositoryTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/ByStockItemRepositoryTest.php new file mode 100644 index 0000000000000..521eff7c89ef8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/ByStockItemRepositoryTest.php @@ -0,0 +1,74 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test stock item saving via stock item repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSave() + { + /** @var ProductInterface $product */ + $product = $this->productRepository->get('simple', false, null, true); + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $this->dataObjectHelper->populateWithArray( + $stockItem, + $this->stockItemData, + StockItemInterface::class + ); + $this->stockItemRepository->save($stockItem); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByQuantityAndStockStatusTest.php new file mode 100644 index 0000000000000..d11e2661c6088 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByQuantityAndStockStatusTest.php @@ -0,0 +1,101 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->get(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product + * model (deprecated) + */ + public function testSaveBySetQuantityAndStockStatus() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setQuantityAndStockStatus($this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated) + * via product model (deprecated) + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setData('quantity_and_stock_status', $this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockDataTest.php new file mode 100644 index 0000000000000..a840b48c18537 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockDataTest.php @@ -0,0 +1,100 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->get(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item on product save by 'setStockData' method (deprecated) via product model (deprecated) + */ + public function testSaveBySetStockData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setStockData($this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated) + * via product model (deprecated) + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setData('stock_data', $this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockItemTest.php new file mode 100644 index 0000000000000..412ddcfd17351 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockItemTest.php @@ -0,0 +1,112 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->create(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item by product data via product model (deprecated) + */ + public function testSave() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $productData = $this->productData; + $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData; + $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually created stock item (and set by extension attributes object) on product save via product + * model (deprecated) + */ + public function testSaveManuallyCreatedStockItem() + { + /** @var StockItemInterface $stockItem */ + $stockItem = $this->stockItemFactory->create(); + $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class); + + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->getExtensionAttributes()->setStockItem($stockItem); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByQuantityAndStockStatusTest.php new file mode 100644 index 0000000000000..d40a78d16d464 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByQuantityAndStockStatusTest.php @@ -0,0 +1,110 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + // prevent internal caching in property + $this->productRepository->cleanCache(); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->get(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product + * repository + */ + public function testSaveBySetQuantityAndStockStatus() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setQuantityAndStockStatus($this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated) + * via product repository + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setData('quantity_and_stock_status', $this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockDataTest.php new file mode 100644 index 0000000000000..271786918209d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockDataTest.php @@ -0,0 +1,109 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + // prevent internal caching in property + $this->productRepository->cleanCache(); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->get(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item on product save by 'setStockData' method (deprecated) via product repository + */ + public function testSaveBySetStockData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setStockData($this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated) + * via product repository + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setData('stock_data', $this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockItemTest.php new file mode 100644 index 0000000000000..daf333a512f3f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockItemTest.php @@ -0,0 +1,144 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class); + $this->stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + // prevent internal caching in property + $this->productRepository->cleanCache(); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->create(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item by product data via product repository + */ + public function testSave() + { + /** @var ProductInterface $product */ + $product = $this->productFactory->create(); + $productData = $this->productData; + $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData; + $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually created stock item (and set by extension attributes object) on product save via product + * repository + */ + public function testSaveManuallyCreatedStockItem() + { + /** @var StockItemInterface $stockItem */ + $stockItem = $this->stockItemFactory->create(); + $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class); + + /** @var ProductInterface $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->getExtensionAttributes()->setStockItem($stockItem); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test automatically stock item creating on product save via product repository + */ + public function testAutomaticallyStockItemCreating() + { + /** @var ProductInterface $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product = $this->productRepository->save($product); + + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class); + $this->stockItemRepository->save($stockItem); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByQuantityAndStockStatusTest.php new file mode 100644 index 0000000000000..bc5c623e4999b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByQuantityAndStockStatusTest.php @@ -0,0 +1,73 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product + * model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetQuantityAndStockStatus() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setQuantityAndStockStatus($this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated) + * via product model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setData('quantity_and_stock_status', $this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockDataTest.php new file mode 100644 index 0000000000000..c2468b690f95b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockDataTest.php @@ -0,0 +1,73 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item on product save by 'setStockData' method (deprecated) via product + * model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetStockData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setStockData($this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated) + * via product model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setData('stock_data', $this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockItemTest.php new file mode 100644 index 0000000000000..59c6d7a4554a7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockItemTest.php @@ -0,0 +1,113 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item by product data via product model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSave() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData; + $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually created stock item (and set by extension attributes object) on product save via + * product model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveManuallyCreatedStockItem() + { + /** @var StockItemInterface $stockItem */ + $stockItem = $this->stockItemFactory->create(); + $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class); + + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->getExtensionAttributes()->setStockItem($stockItem); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually updated stock item (obtained from extension attributes object) on product save via + * product repository (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveManuallyUpdatedStockItem() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $this->dataObjectHelper->populateWithArray( + $stockItem, + $this->stockItemData, + StockItemInterface::class + ); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByQuantityAndStockStatusTest.php new file mode 100644 index 0000000000000..0bfe402505052 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByQuantityAndStockStatusTest.php @@ -0,0 +1,73 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product + * repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetQuantityAndStockStatus() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setQuantityAndStockStatus($this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated) + * via product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setData('quantity_and_stock_status', $this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockDataTest.php new file mode 100644 index 0000000000000..8ff4b89d2480c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockDataTest.php @@ -0,0 +1,72 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item on product save by 'setStockData' method (deprecated) via product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetStockData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setStockData($this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated) + * via product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setData('stock_data', $this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockItemTest.php new file mode 100644 index 0000000000000..8a3786f839902 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockItemTest.php @@ -0,0 +1,120 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item by product data via product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSave() + { + /** @var ProductInterface $product */ + $product = $this->productRepository->get('simple', false, null, true); + $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData; + $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually created stock item (and set by extension attributes object) on product save via + * product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveManuallyCreatedStockItem() + { + /** @var StockItemInterface $stockItem */ + $stockItem = $this->stockItemFactory->create(); + $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class); + + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->getExtensionAttributes()->setStockItem($stockItem); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually updated stock item (obtained from extension attributes object) on product save via + * product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveManuallyUpdatedStockItem() + { + /** @var ProductInterface $product */ + $product = $this->productRepository->get('simple', false, null, true); + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $this->dataObjectHelper->populateWithArray( + $stockItem, + $this->stockItemData, + StockItemInterface::class + ); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/StockItemDataChecker.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/StockItemDataChecker.php new file mode 100644 index 0000000000000..189f8767bac60 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/StockItemDataChecker.php @@ -0,0 +1,131 @@ +hydrator = $hydrator; + $this->stockItemRepository = $stockItemRepository; + $this->stockItemCriteriaFactory = $stockItemCriteriaFactory; + $this->productRepository = $productRepository; + $this->productFactory = $productFactory; + } + + /** + * @param string $sku + * @param array $expectedData + */ + public function checkStockItemData($sku, array $expectedData) + { + $product = $this->productRepository->get($sku, false, null, true); + $this->doCheckStockItemData($product, $expectedData); + + /** @var Product $product */ + $productLoadedByModel = $this->productFactory->create(); + $productLoadedByModel->load($product->getId()); + $this->doCheckStockItemData($product, $expectedData); + } + + /** + * @param Product $product + * @param array $expectedData + */ + private function doCheckStockItemData(Product $product, array $expectedData) + { + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $stockItem = $this->stockItemRepository->get($stockItem->getItemId()); + + $this->assertArrayContains($expectedData, $this->hydrator->extract($stockItem)); + + $criteria = $this->stockItemCriteriaFactory->create(); + $result = $this->stockItemRepository->getList($criteria); + $items = $result->getItems(); + $stockItem = current($items); + $this->assertArrayContains($expectedData, $this->hydrator->extract($stockItem)); + + $expectedQuantityAndStockStatusData = array_intersect_key($expectedData, [ + StockItemInterface::IS_IN_STOCK => 0, + StockItemInterface::QTY => 0, + ]); + \PHPUnit_Framework_Assert::assertNotNull($product->getQuantityAndStockStatus()); + $this->assertArrayContains($expectedQuantityAndStockStatusData, $product->getQuantityAndStockStatus()); + + \PHPUnit_Framework_Assert::assertNotNull($product->getData('quantity_and_stock_status')); + $this->assertArrayContains($expectedQuantityAndStockStatusData, $product->getData('quantity_and_stock_status')); + } + + /** + * @param array $expected + * @param array $actual + * @return void + */ + private function assertArrayContains(array $expected, array $actual) + { + foreach ($expected as $key => $value) { + \PHPUnit_Framework_Assert::assertArrayHasKey( + $key, + $actual, + "Expected value for key '{$key}' is missed" + ); + if (is_array($value)) { + $this->assertArrayContains($value, $actual[$key]); + } else { + \PHPUnit_Framework_Assert::assertEquals( + $value, + $actual[$key], + "Expected value for key '{$key}' doesn't match" + ); + } + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Review/Block/FormTest.php b/dev/tests/integration/testsuite/Magento/Review/Block/FormTest.php new file mode 100644 index 0000000000000..5132a2f9456de --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/Block/FormTest.php @@ -0,0 +1,86 @@ +objectManager = $this->getObjectManager(); + + parent::setUp(); + } + + /** + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Review/_files/config.php + * @dataProvider getCorrectFlagDataProvider + */ + public function testGetCorrectFlag( + $path, + $scope, + $scopeId, + $value, + $expectedResult + ) { + /** @var State $appState */ + $appState = $this->objectManager->get(State::class); + $appState->setAreaCode(Area::AREA_FRONTEND); + + /** @var Value $config */ + $config = $this->objectManager->create(Value::class); + $config->setPath($path); + $config->setScope($scope); + $config->setScopeId($scopeId); + $config->setValue($value); + $config->save(); + /** @var ReinitableConfig $reinitableConfig */ + $reinitableConfig = $this->objectManager->create(ReinitableConfig::class); + $reinitableConfig->reinit(); + + /** @var \Magento\Review\Block\Form $form */ + $form = $this->objectManager->create(\Magento\Review\Block\Form::class); + $result = $form->getAllowWriteReviewFlag(); + $this->assertEquals($result, $expectedResult); + } + + public function getCorrectFlagDataProvider() + { + return [ + [ + 'path' => 'catalog/review/allow_guest', + 'scope' => 'websites', + 'scopeId' => '1', + 'value' => 0, + 'expectedResult' => false, + ], + [ + 'path' => 'catalog/review/allow_guest', + 'scope' => 'websites', + 'scopeId' => '1', + 'value' => 1, + 'expectedResult' => true + ] + ]; + } + + private function getObjectManager() + { + return \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Review/_files/config.php b/dev/tests/integration/testsuite/Magento/Review/_files/config.php new file mode 100644 index 0000000000000..13436974d6305 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/_files/config.php @@ -0,0 +1,15 @@ +create(Value::class); +$config->setPath('catalog/review/allow_guest'); +$config->setScope('default'); +$config->setScopeId(0); +$config->setValue(1); +$config->save(); diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php index d295bb89579ec..4ff3dfde24177 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php @@ -35,7 +35,6 @@ public function tearDown() public function testExecuteConsoleOutput() { - $this->markTestSkipped('MAGETWO-64249: Unexpected test exit on Travis CI'); $this->tester->execute( [ 'directory' => BP . '/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/', diff --git a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php index 9d1ad1095b11a..62b97e54bf0f0 100644 --- a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php @@ -9,7 +9,7 @@ * Test class for \Magento\Sitemap\Model\ResourceModel\Catalog\Product. * - test products collection generation for sitemap * - * @magentoDataFixtureBeforeTransaction Magento/CatalogSearch/_files/full_reindex.php + * @magentoDataFixtureBeforeTransaction Magento/Catalog/_files/enable_reindex_schedule.php * @magentoDataFixture Magento/Sitemap/_files/sitemap_products.php */ class ProductTest extends \PHPUnit_Framework_TestCase diff --git a/lib/internal/Magento/Framework/Console/Cli.php b/lib/internal/Magento/Framework/Console/Cli.php index 27a247107fc33..fb8d3e3f28c3c 100644 --- a/lib/internal/Magento/Framework/Console/Cli.php +++ b/lib/internal/Magento/Framework/Console/Cli.php @@ -11,7 +11,7 @@ use Magento\Framework\App\ProductMetadata; use Magento\Framework\App\State; use Magento\Framework\Composer\ComposerJsonFinder; -use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Console\Exception\GenerationDirectoryAccessException; use Magento\Framework\Filesystem\Driver\File; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Shell\ComplexParameter; @@ -66,17 +66,27 @@ class Cli extends Console\Application /** * @param string $name the application name * @param string $version the application version + * @SuppressWarnings(PHPMD.ExitExpression) */ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') { - $configuration = require BP . '/setup/config/application.config.php'; - $bootstrapApplication = new Application(); - $application = $bootstrapApplication->bootstrap($configuration); - $this->serviceManager = $application->getServiceManager(); + try { + $configuration = require BP . '/setup/config/application.config.php'; + $bootstrapApplication = new Application(); + $application = $bootstrapApplication->bootstrap($configuration); + $this->serviceManager = $application->getServiceManager(); + + $this->assertCompilerPreparation(); + $this->initObjectManager(); + $this->assertGenerationPermissions(); + } catch (\Exception $exception) { + $output = new \Symfony\Component\Console\Output\ConsoleOutput(); + $output->writeln( + '' . $exception->getMessage() . '' + ); - $this->assertCompilerPreparation(); - $this->initObjectManager(); - $this->assertGenerationPermissions(); + exit(static::RETURN_FAILURE); + } if ($version == 'UNKNOWN') { $directoryList = new DirectoryList(BP); @@ -91,20 +101,13 @@ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') /** * {@inheritdoc} * - * @throws \Exception the exception in case of unexpected error + * @throws \Exception The exception in case of unexpected error */ public function doRun(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $exitCode = parent::doRun($input, $output); if ($this->initException) { - $output->writeln( - "We're sorry, an error occurred. Try clearing the cache and code generation directories. " - . "By default, they are: " . $this->getDefaultDirectoryPath(DirectoryList::CACHE) . ", " - . $this->getDefaultDirectoryPath(DirectoryList::GENERATED_METADATA) . ", " - . $this->getDefaultDirectoryPath(DirectoryList::GENERATED_CODE) . ", and var/page_cache." - ); - throw $this->initException; } @@ -154,24 +157,17 @@ protected function getApplicationCommands() * Object Manager initialization. * * @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(); + $params = (new ComplexParameter(self::INPUT_KEY_BOOTSTRAP))->mergeFromArgv($_SERVER, $_SERVER); + $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null; - /** @var ObjectManagerProvider $omProvider */ - $omProvider = $this->serviceManager->get(ObjectManagerProvider::class); - $omProvider->setObjectManager($this->objectManager); - } catch (FileSystemException $exception) { - $this->writeGenerationDirectoryReadError(); + $this->objectManager = Bootstrap::create(BP, $params)->getObjectManager(); - exit(static::RETURN_FAILURE); - } + /** @var ObjectManagerProvider $omProvider */ + $omProvider = $this->serviceManager->get(ObjectManagerProvider::class); + $omProvider->setObjectManager($this->objectManager); } /** @@ -182,7 +178,7 @@ private function initObjectManager() * developer - application will be terminated * * @return void - * @SuppressWarnings(PHPMD.ExitExpression) + * @throws GenerationDirectoryAccessException If generation directory is read-only in developer mode */ private function assertGenerationPermissions() { @@ -197,9 +193,7 @@ private function assertGenerationPermissions() if ($state->getMode() !== State::MODE_PRODUCTION && !$generationDirectoryAccess->check() ) { - $this->writeGenerationDirectoryReadError(); - - exit(static::RETURN_FAILURE); + throw new GenerationDirectoryAccessException(); } } @@ -207,7 +201,7 @@ private function assertGenerationPermissions() * Checks whether compiler is being prepared. * * @return void - * @SuppressWarnings(PHPMD.ExitExpression) + * @throws GenerationDirectoryAccessException If generation directory is read-only */ private function assertCompilerPreparation() { @@ -222,33 +216,10 @@ private function assertCompilerPreparation() new File() ); - try { - $compilerPreparation->handleCompilerEnvironment(); - } catch (FileSystemException $e) { - $this->writeGenerationDirectoryReadError(); - - exit(static::RETURN_FAILURE); - } + $compilerPreparation->handleCompilerEnvironment(); } } - /** - * Writes read error to console. - * - * @return void - */ - private function writeGenerationDirectoryReadError() - { - $output = new \Symfony\Component\Console\Output\ConsoleOutput(); - $output->writeln( - '' - . 'Command line user does not have read and write permissions on ' - . $this->getDefaultDirectoryPath(DirectoryList::GENERATED_CODE) . ' directory. ' - . 'Please address this issue before using Magento command line.' - . '' - ); - } - /** * Retrieves vendor commands. * @@ -270,22 +241,4 @@ protected function getVendorCommands($objectManager) 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/Exception/GenerationDirectoryAccessException.php b/lib/internal/Magento/Framework/Console/Exception/GenerationDirectoryAccessException.php new file mode 100644 index 0000000000000..fc65cc0362e24 --- /dev/null +++ b/lib/internal/Magento/Framework/Console/Exception/GenerationDirectoryAccessException.php @@ -0,0 +1,48 @@ +getDefaultDirectoryPath(DirectoryList::GENERATED) . ' directory. ' + . 'Please address this issue before using Magento command line.' + ); + + parent::__construct($phrase, $cause, $code); + } + + /** + * 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 7349872ff4ac1..b8978c9ef7181 100644 --- a/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php +++ b/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php @@ -7,9 +7,8 @@ use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem\Directory\WriteFactory; use Magento\Framework\Filesystem\DriverPool; -use Magento\Framework\Filesystem\File\WriteFactory; -use Magento\Framework\Filesystem\Directory\Write; use Zend\ServiceManager\ServiceManager; use Magento\Setup\Mvc\Bootstrap\InitParamListener; @@ -33,7 +32,7 @@ public function __construct( } /** - * Check generated/code read and write access + * Check write permissions to generation folders * * @return bool */ @@ -44,33 +43,32 @@ public function check() ? $initParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS] : []; $directoryList = new DirectoryList(BP, $filesystemDirPaths); - $generationDirectoryPath = $directoryList->getPath(DirectoryList::GENERATED_CODE); $driverPool = new DriverPool(); $fileWriteFactory = new WriteFactory($driverPool); - /** @var \Magento\Framework\Filesystem\DriverInterface $driver */ - $driver = $driverPool->getDriver(DriverPool::FILE); - $directoryWrite = new Write($fileWriteFactory, $driver, $generationDirectoryPath); - if ($directoryWrite->isExist()) { - if ($directoryWrite->isDirectory() - || $directoryWrite->isReadable() - ) { + + $generationDirs = [ + DirectoryList::GENERATED, + DirectoryList::GENERATED_CODE, + DirectoryList::GENERATED_METADATA + ]; + + foreach ($generationDirs as $generationDirectory) { + $directoryPath = $directoryList->getPath($generationDirectory); + $directoryWrite = $fileWriteFactory->create($directoryPath); + + if (!$directoryWrite->isExist()) { try { - $probeFilePath = $generationDirectoryPath . DIRECTORY_SEPARATOR . uniqid(mt_rand()).'tmp'; - $fileWriteFactory->create($probeFilePath, DriverPool::FILE, 'w'); - $driver->deleteFile($probeFilePath); + $directoryWrite->create(); } catch (\Exception $e) { return false; } - } else { - return false; } - } else { - try { - $directoryWrite->create(); - } catch (\Exception $e) { + + if (!$directoryWrite->isWritable()) { return false; } } + return true; } } diff --git a/lib/internal/Magento/Framework/Console/Test/Unit/Exception/GenerationDirectoryAccessExceptionTest.php b/lib/internal/Magento/Framework/Console/Test/Unit/Exception/GenerationDirectoryAccessExceptionTest.php new file mode 100644 index 0000000000000..fcc12c8a76bd3 --- /dev/null +++ b/lib/internal/Magento/Framework/Console/Test/Unit/Exception/GenerationDirectoryAccessExceptionTest.php @@ -0,0 +1,21 @@ +assertContains( + 'Command line user does not have read and write permissions on generated directory.', + $exception->getMessage() + ); + } +} diff --git a/setup/src/Magento/Setup/Console/CompilerPreparation.php b/setup/src/Magento/Setup/Console/CompilerPreparation.php index 6bdddfe0053a8..9ea938d51fb37 100644 --- a/setup/src/Magento/Setup/Console/CompilerPreparation.php +++ b/setup/src/Magento/Setup/Console/CompilerPreparation.php @@ -7,6 +7,7 @@ use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Console\Exception\GenerationDirectoryAccessException; use Magento\Framework\Console\GenerationDirectoryAccess; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Filesystem\Driver\File; @@ -59,31 +60,31 @@ public function __construct( /** * Determine whether a CLI command is for compilation, and if so, clear the directory. * - * @throws FileSystemException if generation directory is read-only + * @throws GenerationDirectoryAccessException If generation directory is read-only * @return void */ public function handleCompilerEnvironment() { - $compilationCommands = [DiCompileCommand::NAME]; + $compilationCommands = $this->getCompilerInvalidationCommands(); $cmdName = $this->input->getFirstArgument(); $isHelpOption = $this->input->hasParameterOption('--help') || $this->input->hasParameterOption('-h'); if (!in_array($cmdName, $compilationCommands) || $isHelpOption) { return; } - $compileDirList = []; + + if (!$this->getGenerationDirectoryAccess()->check()) { + throw new GenerationDirectoryAccessException(); + } + $mageInitParams = $this->serviceManager->get(InitParamListener::BOOTSTRAP_PARAM); $mageDirs = isset($mageInitParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS]) ? $mageInitParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS] : []; $directoryList = new DirectoryList(BP, $mageDirs); - $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATED_CODE); - $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATED_METADATA); - - if (!$this->getGenerationDirectoryAccess()->check()) { - throw new FileSystemException( - new Phrase('Generation directory can not be written.') - ); - } + $compileDirList = [ + $directoryList->getPath(DirectoryList::GENERATED_CODE), + $directoryList->getPath(DirectoryList::GENERATED_METADATA), + ]; foreach ($compileDirList as $compileDir) { if ($this->filesystemDriver->isExists($compileDir)) { @@ -92,6 +93,22 @@ public function handleCompilerEnvironment() } } + /** + * Retrieves command list with commands which invalidates compiler + * + * @return array + */ + private function getCompilerInvalidationCommands() + { + return [ + DiCompileCommand::NAME, + 'module:disable', + 'module:enable', + 'module:uninstall', + 'deploy:mode:set' + ]; + } + /** * Retrieves generation directory access checker. *