diff --git a/app/code/Magento/Backend/Model/Url.php b/app/code/Magento/Backend/Model/Url.php index f199fd0fe7bf1..ba0c3d1cfacee 100644 --- a/app/code/Magento/Backend/Model/Url.php +++ b/app/code/Magento/Backend/Model/Url.php @@ -13,6 +13,7 @@ * Class \Magento\Backend\Model\UrlInterface * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @api * @since 100.0.2 */ @@ -366,6 +367,19 @@ protected function _getMenu() return $this->_menu; } + /** + * Set scope entity + * + * @param mixed $scopeId + * @return \Magento\Framework\UrlInterface + */ + public function setScope($scopeId) + { + parent::setScope($scopeId); + $this->_scope = $this->_scopeResolver->getScope($scopeId); + return $this; + } + /** * Set custom auth session * @@ -402,13 +416,13 @@ public function getAreaFrontName() } /** - * Retrieve action path. - * Add backend area front name as a prefix to action path + * Retrieve action path, add backend area front name as a prefix to action path * * @return string */ protected function _getActionPath() { + $path = parent::_getActionPath(); if ($path) { if ($this->getAreaFrontName()) { @@ -448,8 +462,7 @@ protected function _getConfigCacheId($path) } /** - * Get config data by path - * Use only global config values for backend + * Get config data by path, use only global config values for backend * * @param string $path * @return null|string diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php index 063503682f4db..f5d0ec7da617e 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php @@ -19,6 +19,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\FileSystemException; use Magento\Backend\Block\DataProviders\ImageUploadConfig as ImageUploadConfigDataProvider; +use Magento\MediaStorage\Helper\File\Storage\Database; /** * Block for gallery content. @@ -50,25 +51,34 @@ class Content extends \Magento\Backend\Block\Widget */ private $imageUploadConfigDataProvider; + /** + * @var Database + */ + private $fileStorageDatabase; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder * @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig * @param array $data * @param ImageUploadConfigDataProvider $imageUploadConfigDataProvider + * @param Database $fileStorageDatabase */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Framework\Json\EncoderInterface $jsonEncoder, \Magento\Catalog\Model\Product\Media\Config $mediaConfig, array $data = [], - ImageUploadConfigDataProvider $imageUploadConfigDataProvider = null + ImageUploadConfigDataProvider $imageUploadConfigDataProvider = null, + Database $fileStorageDatabase = null ) { $this->_jsonEncoder = $jsonEncoder; $this->_mediaConfig = $mediaConfig; parent::__construct($context, $data); $this->imageUploadConfigDataProvider = $imageUploadConfigDataProvider ?: ObjectManager::getInstance()->get(ImageUploadConfigDataProvider::class); + $this->fileStorageDatabase = $fileStorageDatabase + ?: ObjectManager::getInstance()->get(Database::class); } /** @@ -164,6 +174,13 @@ public function getImagesJson() $images = $this->sortImagesByPosition($value['images']); foreach ($images as &$image) { $image['url'] = $this->_mediaConfig->getMediaUrl($image['file']); + if ($this->fileStorageDatabase->checkDbUsage() && + !$mediaDir->isFile($this->_mediaConfig->getMediaPath($image['file'])) + ) { + $this->fileStorageDatabase->saveFileToFilesystem( + $this->_mediaConfig->getMediaPath($image['file']) + ); + } try { $fileHandler = $mediaDir->stat($this->_mediaConfig->getMediaPath($image['file'])); $image['size'] = $fileHandler['size']; @@ -187,9 +204,12 @@ public function getImagesJson() private function sortImagesByPosition($images) { if (is_array($images)) { - usort($images, function ($imageA, $imageB) { - return ($imageA['position'] < $imageB['position']) ? -1 : 1; - }); + usort( + $images, + function ($imageA, $imageB) { + return ($imageA['position'] < $imageB['position']) ? -1 : 1; + } + ); } return $images; } diff --git a/app/code/Magento/Catalog/Model/Category/FileInfo.php b/app/code/Magento/Catalog/Model/Category/FileInfo.php index 9715bb2b1616e..cd3592ef40a0f 100644 --- a/app/code/Magento/Catalog/Model/Category/FileInfo.php +++ b/app/code/Magento/Catalog/Model/Category/FileInfo.php @@ -43,6 +43,11 @@ class FileInfo */ private $baseDirectory; + /** + * @var ReadInterface + */ + private $pubDirectory; + /** * @param Filesystem $filesystem * @param Mime $mime @@ -82,6 +87,20 @@ private function getBaseDirectory() return $this->baseDirectory; } + /** + * Get Pub Directory read instance + * + * @return ReadInterface + */ + private function getPubDirectory() + { + if (!isset($this->pubDirectory)) { + $this->pubDirectory = $this->filesystem->getDirectoryRead(DirectoryList::PUB); + } + + return $this->pubDirectory; + } + /** * Retrieve MIME type of requested file * @@ -135,7 +154,7 @@ private function getFilePath($fileName) { $filePath = ltrim($fileName, '/'); - $mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath(); + $mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath($filePath); $isFileNameBeginsWithMediaDirectoryPath = $this->isBeginsWithMediaDirectoryPath($fileName); // if the file is not using a relative path, it resides in the catalog/category media directory @@ -160,7 +179,7 @@ public function isBeginsWithMediaDirectoryPath($fileName) { $filePath = ltrim($fileName, '/'); - $mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath(); + $mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath($filePath); $isFileNameBeginsWithMediaDirectoryPath = strpos($filePath, $mediaDirectoryRelativeSubpath) === 0; return $isFileNameBeginsWithMediaDirectoryPath; @@ -169,14 +188,22 @@ public function isBeginsWithMediaDirectoryPath($fileName) /** * Get media directory subpath relative to base directory path * + * @param string $filePath * @return string */ - private function getMediaDirectoryPathRelativeToBaseDirectoryPath() + private function getMediaDirectoryPathRelativeToBaseDirectoryPath(string $filePath = '') { - $baseDirectoryPath = $this->getBaseDirectory()->getAbsolutePath(); + $baseDirectory = $this->getBaseDirectory(); + $baseDirectoryPath = $baseDirectory->getAbsolutePath(); $mediaDirectoryPath = $this->getMediaDirectory()->getAbsolutePath(); + $pubDirectoryPath = $this->getPubDirectory()->getAbsolutePath(); $mediaDirectoryRelativeSubpath = substr($mediaDirectoryPath, strlen($baseDirectoryPath)); + $pubDirectory = $baseDirectory->getRelativePath($pubDirectoryPath); + + if (strpos($mediaDirectoryRelativeSubpath, $pubDirectory) === 0 && strpos($filePath, $pubDirectory) !== 0) { + $mediaDirectoryRelativeSubpath = substr($mediaDirectoryRelativeSubpath, strlen($pubDirectory)); + } return $mediaDirectoryRelativeSubpath; } diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php index 249c32ff276c3..9a2199859a1df 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php @@ -9,6 +9,7 @@ use Magento\Catalog\Model\Entity\Attribute; use Magento\Catalog\Model\Product; use Magento\Framework\Phrase; +use Magento\MediaStorage\Helper\File\Storage\Database; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -50,6 +51,11 @@ class ContentTest extends \PHPUnit\Framework\TestCase */ protected $imageHelper; + /** + * @var \Magento\MediaStorage\Helper\File\Storage\Database|\PHPUnit_Framework_MockObject_MockObject + */ + protected $databaseMock; + /** * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ @@ -71,13 +77,18 @@ public function setUp() ->disableOriginalConstructor() ->getMock(); + $this->databaseMock = $this->getMockBuilder(Database::class) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->content = $this->objectManager->getObject( \Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery\Content::class, [ 'mediaConfig' => $this->mediaConfigMock, 'jsonEncoder' => $this->jsonEncoderMock, - 'filesystem' => $this->fileSystemMock + 'filesystem' => $this->fileSystemMock, + 'fileStorageDatabase' => $this->databaseMock ] ); } @@ -143,6 +154,13 @@ public function testGetImagesJson() $this->readMock->expects($this->any())->method('stat')->willReturnMap($sizeMap); $this->jsonEncoderMock->expects($this->once())->method('encode')->willReturnCallback('json_encode'); + $this->readMock->expects($this->any()) + ->method('isFile') + ->will($this->returnValue(true)); + $this->databaseMock->expects($this->any()) + ->method('checkDbUsage') + ->will($this->returnValue(false)); + $this->assertSame(json_encode($imagesResult), $this->content->getImagesJson()); } @@ -210,6 +228,14 @@ public function testGetImagesJsonWithException() $this->fileSystemMock->expects($this->any())->method('getDirectoryRead')->willReturn($this->readMock); $this->mediaConfigMock->expects($this->any())->method('getMediaUrl'); $this->mediaConfigMock->expects($this->any())->method('getMediaPath'); + + $this->readMock->expects($this->any()) + ->method('isFile') + ->will($this->returnValue(true)); + $this->databaseMock->expects($this->any()) + ->method('checkDbUsage') + ->will($this->returnValue(false)); + $this->readMock->expects($this->any())->method('stat')->willReturnOnConsecutiveCalls( $this->throwException( new \Magento\Framework\Exception\FileSystemException(new Phrase('test')) @@ -365,4 +391,52 @@ private function getMediaAttribute(string $label, string $attributeCode) return $mediaAttribute; } + + /** + * Test GetImagesJson() calls MediaStorage functions to obtain image from DB prior to stat call + * + * @return void + */ + public function testGetImagesJsonMediaStorageMode() + { + $images = [ + 'images' => [ + [ + 'value_id' => '0', + 'file' => 'file_1.jpg', + 'media_type' => 'image', + 'position' => '0' + ] + ] + ]; + + $mediaPath = [ + ['file_1.jpg', 'catalog/product/image_1.jpg'] + ]; + + $this->content->setElement($this->galleryMock); + + $this->galleryMock->expects($this->once()) + ->method('getImages') + ->willReturn($images); + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->willReturn($this->readMock); + $this->mediaConfigMock->expects($this->any()) + ->method('getMediaPath') + ->willReturnMap($mediaPath); + + $this->readMock->expects($this->any()) + ->method('isFile') + ->will($this->returnValue(false)); + $this->databaseMock->expects($this->any()) + ->method('checkDbUsage') + ->will($this->returnValue(true)); + + $this->databaseMock->expects($this->once()) + ->method('saveFileToFilesystem') + ->with('catalog/product/image_1.jpg'); + + $this->content->getImagesJson(); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php index 8ca823127e66c..6c6a69ec39c85 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php @@ -9,31 +9,41 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\File\Mime; use Magento\Framework\Filesystem; -use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; -class FileInfoTest extends \PHPUnit\Framework\TestCase +/** + * Test for Magento\Catalog\Model\Category\FileInfo class. + */ +class FileInfoTest extends TestCase { /** - * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject + * @var Filesystem|MockObject */ private $filesystem; /** - * @var Mime|\PHPUnit_Framework_MockObject_MockObject + * @var Mime|MockObject */ private $mime; /** - * @var WriteInterface|\PHPUnit_Framework_MockObject_MockObject + * @var WriteInterface|MockObject */ private $mediaDirectory; /** - * @var ReadInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ReadInterface|MockObject */ private $baseDirectory; + /** + * @var ReadInterface|MockObject + */ + private $pubDirectory; + /** * @var FileInfo */ @@ -44,30 +54,43 @@ protected function setUp() $this->mediaDirectory = $this->getMockBuilder(WriteInterface::class) ->getMockForAbstractClass(); - $this->baseDirectory = $this->getMockBuilder(ReadInterface::class) + $this->baseDirectory = $baseDirectory = $this->getMockBuilder(ReadInterface::class) + ->getMockForAbstractClass(); + + $this->pubDirectory = $pubDirectory = $this->getMockBuilder(ReadInterface::class) ->getMockForAbstractClass(); $this->filesystem = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor() ->getMock(); - $this->filesystem->expects($this->any()) - ->method('getDirectoryWrite') + + $this->filesystem->method('getDirectoryWrite') ->with(DirectoryList::MEDIA) ->willReturn($this->mediaDirectory); - $this->filesystem->expects($this->any()) - ->method('getDirectoryRead') - ->with(DirectoryList::ROOT) - ->willReturn($this->baseDirectory); + $this->filesystem->method('getDirectoryRead') + ->willReturnCallback( + function ($arg) use ($baseDirectory, $pubDirectory) { + if ($arg === DirectoryList::PUB) { + return $pubDirectory; + } + return $baseDirectory; + } + ); $this->mime = $this->getMockBuilder(Mime::class) ->disableOriginalConstructor() ->getMock(); - $this->baseDirectory->expects($this->any()) - ->method('getAbsolutePath') - ->with(null) - ->willReturn('/a/b/c'); + $this->baseDirectory->method('getAbsolutePath') + ->willReturn('/a/b/c/'); + + $this->baseDirectory->method('getRelativePath') + ->with('/a/b/c/pub/') + ->willReturn('pub/'); + + $this->pubDirectory->method('getAbsolutePath') + ->willReturn('/a/b/c/pub/'); $this->model = new FileInfo( $this->filesystem, @@ -85,12 +108,12 @@ public function testGetMimeType() $this->mediaDirectory->expects($this->at(0)) ->method('getAbsolutePath') ->with(null) - ->willReturn('/a/b/c/pub/media'); + ->willReturn('/a/b/c/pub/media/'); $this->mediaDirectory->expects($this->at(1)) ->method('getAbsolutePath') ->with(null) - ->willReturn('/a/b/c/pub/media'); + ->willReturn('/a/b/c/pub/media/'); $this->mediaDirectory->expects($this->at(2)) ->method('getAbsolutePath') @@ -113,13 +136,11 @@ public function testGetStat() $expected = ['size' => 1]; - $this->mediaDirectory->expects($this->any()) - ->method('getAbsolutePath') + $this->mediaDirectory->method('getAbsolutePath') ->with(null) - ->willReturn('/a/b/c/pub/media'); + ->willReturn('/a/b/c/pub/media/'); - $this->mediaDirectory->expects($this->once()) - ->method('stat') + $this->mediaDirectory->method('stat') ->with($mediaPath . $fileName) ->willReturn($expected); @@ -130,22 +151,52 @@ public function testGetStat() $this->assertEquals(1, $result['size']); } - public function testIsExist() + /** + * @param $fileName + * @param $fileMediaPath + * @dataProvider isExistProvider + */ + public function testIsExist($fileName, $fileMediaPath) { - $mediaPath = '/catalog/category'; + $this->mediaDirectory->method('getAbsolutePath') + ->willReturn('/a/b/c/pub/media/'); - $fileName = '/filename.ext1'; - - $this->mediaDirectory->expects($this->any()) - ->method('getAbsolutePath') - ->with(null) - ->willReturn('/a/b/c/pub/media'); - - $this->mediaDirectory->expects($this->once()) - ->method('isExist') - ->with($mediaPath . $fileName) + $this->mediaDirectory->method('isExist') + ->with($fileMediaPath) ->willReturn(true); $this->assertTrue($this->model->isExist($fileName)); } + + public function isExistProvider() + { + return [ + ['/filename.ext1', '/catalog/category/filename.ext1'], + ['/pub/media/filename.ext1', 'filename.ext1'], + ['/media/filename.ext1', 'filename.ext1'] + ]; + } + + /** + * @param $fileName + * @param $expected + * @dataProvider isBeginsWithMediaDirectoryPathProvider + */ + public function testIsBeginsWithMediaDirectoryPath($fileName, $expected) + { + $this->mediaDirectory->method('getAbsolutePath') + ->willReturn('/a/b/c/pub/media/'); + + $this->assertEquals($expected, $this->model->isBeginsWithMediaDirectoryPath($fileName)); + } + + public function isBeginsWithMediaDirectoryPathProvider() + { + return [ + ['/pub/media/test/filename.ext1', true], + ['/media/test/filename.ext1', true], + ['/test/filename.ext1', false], + ['test2/filename.ext1', false] + ]; + } } diff --git a/app/code/Magento/Newsletter/Block/Adminhtml/Problem.php b/app/code/Magento/Newsletter/Block/Adminhtml/Problem.php index 61a17d7ad5e51..0c66e46a850ee 100644 --- a/app/code/Magento/Newsletter/Block/Adminhtml/Problem.php +++ b/app/code/Magento/Newsletter/Block/Adminhtml/Problem.php @@ -41,7 +41,7 @@ public function __construct( } /** - * @return void + * @inheritDoc * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ protected function _construct() @@ -83,7 +83,7 @@ protected function _prepareLayout() /** * Get the html element for unsubscribe button * - * @return $string + * @return string */ public function getUnsubscribeButtonHtml() { @@ -93,7 +93,7 @@ public function getUnsubscribeButtonHtml() /** * Get the html element for delete button * - * @return $string + * @return string */ public function getDeleteButtonHtml() { diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php index 7f02e4ea13445..cdef44b2da757 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php @@ -6,22 +6,27 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Subscriber; -use Magento\Newsletter\Controller\Adminhtml\Subscriber; use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\Response\Http\FileFactory; +use Magento\Newsletter\Controller\Adminhtml\Subscriber; use Magento\Newsletter\Model\SubscriberFactory; -use Magento\Framework\App\ObjectManager; -class MassDelete extends Subscriber +/** + * Subscriber mass delete controller. + */ +class MassDelete extends Subscriber implements HttpPostActionInterface { /** * @var SubscriberFactory */ private $subscriberFactory; - + /** * @param Context $context * @param FileFactory $fileFactory + * @param SubscriberFactory|null $subscriberFactory */ public function __construct( Context $context, @@ -31,7 +36,7 @@ public function __construct( $this->subscriberFactory = $subscriberFactory ?: ObjectManager::getInstance()->get(SubscriberFactory::class); parent::__construct($context, $fileFactory); } - + /** * Delete one or more subscribers action * diff --git a/app/code/Magento/Newsletter/Controller/Manage/Save.php b/app/code/Magento/Newsletter/Controller/Manage/Save.php index 698c2d19aae68..d7d511e2d1906 100644 --- a/app/code/Magento/Newsletter/Controller/Manage/Save.php +++ b/app/code/Magento/Newsletter/Controller/Manage/Save.php @@ -3,11 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Newsletter\Controller\Manage; use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository; -use Magento\Customer\Model\Customer; +use Magento\Customer\Api\Data\CustomerInterface; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Newsletter\Model\Subscriber; @@ -65,7 +66,7 @@ public function __construct( /** * Save newsletter subscription preference action * - * @return void|null + * @return \Magento\Framework\App\ResponseInterface */ public function execute() { @@ -110,16 +111,16 @@ public function execute() $this->messageManager->addError(__('Something went wrong while saving your subscription.')); } } - $this->_redirect('customer/account/'); + return $this->_redirect('customer/account/'); } /** * Set ignore_validation_flag to skip unnecessary address and customer validation * - * @param Customer $customer + * @param CustomerInterface $customer * @return void */ - private function setIgnoreValidationFlag($customer) + private function setIgnoreValidationFlag(CustomerInterface $customer): void { $customer->setData('ignore_validation_flag', true); } diff --git a/app/code/Magento/Newsletter/Controller/Subscriber/Confirm.php b/app/code/Magento/Newsletter/Controller/Subscriber/Confirm.php index c27717f4c7793..6f566761b2f87 100644 --- a/app/code/Magento/Newsletter/Controller/Subscriber/Confirm.php +++ b/app/code/Magento/Newsletter/Controller/Subscriber/Confirm.php @@ -4,6 +4,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Newsletter\Controller\Subscriber; diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index e7e5d5f202811..117783495406a 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -7,11 +7,11 @@ use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\CustomerRepositoryInterface; -use Magento\Framework\Exception\MailException; -use Magento\Framework\Exception\NoSuchEntityException; use Magento\Customer\Api\Data\CustomerInterfaceFactory; use Magento\Framework\Api\DataObjectHelper; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\MailException; +use Magento\Framework\Exception\NoSuchEntityException; /** * Subscriber model @@ -31,6 +31,7 @@ * @method int getSubscriberId() * @method Subscriber setSubscriberId(int $value) * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @@ -402,6 +403,7 @@ public function loadByCustomerId($customerId) $this->setSubscriberConfirmCode($this->randomSequence()); $this->save(); } + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock } catch (NoSuchEntityException $e) { } return $this; @@ -493,7 +495,9 @@ public function subscribe($email) $this->sendConfirmationSuccessEmail(); } return $this->getStatus(); + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception($e->getMessage()); } } @@ -559,7 +563,7 @@ public function updateSubscription($customerId) * * @param int $customerId * @param bool $subscribe indicates whether the customer should be subscribed or unsubscribed - * @return $this + * @return $this * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -686,7 +690,7 @@ public function confirm($code) * Mark receiving subscriber of queue newsletter * * @param \Magento\Newsletter\Model\Queue $queue - * @return boolean + * @return Subscriber */ public function received(\Magento\Newsletter\Model\Queue $queue) { diff --git a/app/code/Magento/Reports/Model/ResourceModel/Product/Sold/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Product/Sold/Collection.php index 61dc77d188438..35a14e09e314f 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Product/Sold/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Product/Sold/Collection.php @@ -14,6 +14,8 @@ use Magento\Framework\DB\Select; /** + * Data collection. + * * @SuppressWarnings(PHPMD.DepthOfInheritance) * @api * @since 100.0.2 @@ -21,7 +23,7 @@ class Collection extends \Magento\Reports\Model\ResourceModel\Order\Collection { /** - * Set Date range to collection + * Set Date range to collection. * * @param int $from * @param int $to @@ -79,6 +81,10 @@ public function addOrderedQty($from = '', $to = '') )->having( 'order_items.qty_ordered > ?', 0 + )->columns( + 'SUM(order_items.qty_ordered) as ordered_qty' + )->group( + 'order_items.product_id' ); return $this; } @@ -116,6 +122,8 @@ public function setOrder($attribute, $dir = self::SORT_ORDER_DESC) } /** + * @inheritdoc + * * @return Select * @since 100.2.0 */ diff --git a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js index cc0924ca20677..5f20325eb686e 100644 --- a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js +++ b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js @@ -573,6 +573,9 @@ define([ applyCoupon : function(code){ this.loadArea(['items', 'shipping_method', 'totals', 'billing_method'], true, {'order[coupon][code]':code, reset_shipping: 0}); this.orderItemChanged = false; + jQuery('html, body').animate({ + scrollTop: 0 + }); }, addProduct : function(id){ diff --git a/app/code/Magento/SendFriend/Controller/Product.php b/app/code/Magento/SendFriend/Controller/Product.php index 732bcef8b957a..a184d7d8bff62 100644 --- a/app/code/Magento/SendFriend/Controller/Product.php +++ b/app/code/Magento/SendFriend/Controller/Product.php @@ -61,6 +61,7 @@ public function __construct( /** * Check if module is enabled + * * If allow only for customer - redirect to login page * * @param RequestInterface $request @@ -102,7 +103,7 @@ protected function _initProduct() } try { $product = $this->productRepository->getById($productId); - if (!$product->isVisibleInCatalog()) { + if (!$product->isVisibleInSiteVisibility() || !$product->isVisibleInCatalog()) { return false; } } catch (NoSuchEntityException $noEntityException) { diff --git a/app/code/Magento/Ui/view/base/web/js/form/form.js b/app/code/Magento/Ui/view/base/web/js/form/form.js index ea6c57f63bdf1..3692108675bc7 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/form.js +++ b/app/code/Magento/Ui/view/base/web/js/form/form.js @@ -339,6 +339,7 @@ define([ */ reset: function () { this.source.trigger('data.reset'); + $('[data-bind*=datepicker]').val(''); }, /** diff --git a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php index 855f455120d89..847def6e8922c 100644 --- a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php +++ b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php @@ -41,6 +41,11 @@ class Generator extends AbstractSchemaGenerator /** Array signifier */ const ARRAY_SIGNIFIER = '[0]'; + /** + * Wrapper node for XML requests + */ + private const XML_SCHEMA_PARAMWRAPPER = 'request'; + /** * Swagger factory instance. * @@ -134,7 +139,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ protected function generateSchema($requestedServiceMetadata, $requestScheme, $requestHost, $endpointUrl) { @@ -193,6 +198,32 @@ protected function getGeneralInfo() ]; } + /** + * List out consumes data type + * + * @return array + */ + private function getConsumableDatatypes() + { + return [ + 'application/json', + 'application/xml', + ]; + } + + /** + * List out produces data type + * + * @return array + */ + private function getProducibleDatatypes() + { + return [ + 'application/json', + 'application/xml', + ]; + } + /** * Generate path info based on method data * @@ -212,6 +243,8 @@ protected function generatePathInfo($methodName, $httpMethodData, $tagName) 'tags' => [$tagName], 'description' => $methodData['documentation'], 'operationId' => $operationId, + 'consumes' => $this->getConsumableDatatypes(), + 'produces' => $this->getProducibleDatatypes(), ]; $parameters = $this->generateMethodParameters($httpMethodData, $operationId); @@ -602,7 +635,7 @@ protected function getDefinitionReference($typeName) /** * Get the CamelCased type name in 'hyphen-separated-lowercase-words' format * - * e.g. test-module5-v1-entity-all-soap-and-rest + * E.g. test-module5-v1-entity-all-soap-and-rest * * @param string $typeName * @return string @@ -842,6 +875,17 @@ private function generateBodySchema($parameterName, $parameterInfo, $description $description ); $bodySchema['type'] = 'object'; + + /* + * Make sure we have a proper XML wrapper for request parameters for the XML format. + */ + if (!isset($bodySchema['xml']) || !is_array($bodySchema['xml'])) { + $bodySchema['xml'] = []; + } + if (!isset($bodySchema['xml']['name']) || empty($bodySchema['xml']['name'])) { + $bodySchema['xml']['name'] = self::XML_SCHEMA_PARAMWRAPPER; + } + return $bodySchema; } diff --git a/app/code/Magento/Webapi/Test/Unit/Model/Rest/Swagger/GeneratorTest.php b/app/code/Magento/Webapi/Test/Unit/Model/Rest/Swagger/GeneratorTest.php index 66b59babb7189..172db875c6c49 100644 --- a/app/code/Magento/Webapi/Test/Unit/Model/Rest/Swagger/GeneratorTest.php +++ b/app/code/Magento/Webapi/Test/Unit/Model/Rest/Swagger/GeneratorTest.php @@ -223,7 +223,7 @@ public function generateDataProvider() ] ], // @codingStandardsIgnoreStart - '{"swagger":"2.0","info":{"version":"","title":""},"host":"magento.host","basePath":"/rest/default","schemes":["http://"],"tags":[{"name":"testModule5AllSoapAndRestV2","description":"AllSoapAndRestInterface"}],"paths":{"/V1/testModule5":{"post":{"tags":["testModule5AllSoapAndRestV2"],"description":"Add new item.","operationId":"' . self::OPERATION_NAME . 'Post","parameters":[{"name":"operationNamePostBody","in":"body","schema":{"required":["item"],"properties":{"item":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}}},"definitions":{"error-response":{"type":"object","properties":{"message":{"type":"string","description":"Error message"},"errors":{"$ref":"#/definitions/error-errors"},"code":{"type":"integer","description":"Error code"},"parameters":{"$ref":"#/definitions/error-parameters"},"trace":{"type":"string","description":"Stack trace"}},"required":["message"]},"error-errors":{"type":"array","description":"Errors list","items":{"$ref":"#/definitions/error-errors-item"}},"error-errors-item":{"type":"object","description":"Error details","properties":{"message":{"type":"string","description":"Error message"},"parameters":{"$ref":"#/definitions/error-parameters"}}},"error-parameters":{"type":"array","description":"Error parameters list","items":{"$ref":"#/definitions/error-parameters-item"}},"error-parameters-item":{"type":"object","description":"Error parameters item","properties":{"resources":{"type":"string","description":"ACL resource"},"fieldName":{"type":"string","description":"Missing or invalid field name"},"fieldValue":{"type":"string","description":"Incorrect field value"}}},"test-module5-v2-entity-all-soap-and-rest":{"type":"object","description":"Some Data Object","properties":{"price":{"type":"integer"}},"required":["price"]}}}' + '{"swagger":"2.0","info":{"version":"","title":""},"host":"magento.host","basePath":"/rest/default","schemes":["http://"],"tags":[{"name":"testModule5AllSoapAndRestV2","description":"AllSoapAndRestInterface"}],"paths":{"/V1/testModule5":{"post":{"tags":["testModule5AllSoapAndRestV2"],"description":"Add new item.","operationId":"operationNamePost","consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"parameters":[{"name":"operationNamePostBody","in":"body","schema":{"required":["item"],"properties":{"item":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"type":"object","xml":{"name":"request"}}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}}},"definitions":{"error-response":{"type":"object","properties":{"message":{"type":"string","description":"Error message"},"errors":{"$ref":"#/definitions/error-errors"},"code":{"type":"integer","description":"Error code"},"parameters":{"$ref":"#/definitions/error-parameters"},"trace":{"type":"string","description":"Stack trace"}},"required":["message"]},"error-errors":{"type":"array","description":"Errors list","items":{"$ref":"#/definitions/error-errors-item"}},"error-errors-item":{"type":"object","description":"Error details","properties":{"message":{"type":"string","description":"Error message"},"parameters":{"$ref":"#/definitions/error-parameters"}}},"error-parameters":{"type":"array","description":"Error parameters list","items":{"$ref":"#/definitions/error-parameters-item"}},"error-parameters-item":{"type":"object","description":"Error parameters item","properties":{"resources":{"type":"string","description":"ACL resource"},"fieldName":{"type":"string","description":"Missing or invalid field name"},"fieldValue":{"type":"string","description":"Incorrect field value"}}},"test-module5-v2-entity-all-soap-and-rest":{"type":"object","description":"Some Data Object","properties":{"price":{"type":"integer"}},"required":["price"]}}}' // @codingStandardsIgnoreEnd ], [ @@ -271,7 +271,7 @@ public function generateDataProvider() ] ], // @codingStandardsIgnoreStart - '{"swagger":"2.0","info":{"version":"","title":""},"host":"magento.host","basePath":"/rest/default","schemes":["http://"],"tags":[{"name":"testModule5AllSoapAndRestV2","description":"AllSoapAndRestInterface"}],"paths":{"/V1/testModule5":{"get":{"tags":["testModule5AllSoapAndRestV2"],"description":"Retrieve existing item.","operationId":"' . self::OPERATION_NAME . 'Get","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}}},"definitions":{"error-response":{"type":"object","properties":{"message":{"type":"string","description":"Error message"},"errors":{"$ref":"#/definitions/error-errors"},"code":{"type":"integer","description":"Error code"},"parameters":{"$ref":"#/definitions/error-parameters"},"trace":{"type":"string","description":"Stack trace"}},"required":["message"]},"error-errors":{"type":"array","description":"Errors list","items":{"$ref":"#/definitions/error-errors-item"}},"error-errors-item":{"type":"object","description":"Error details","properties":{"message":{"type":"string","description":"Error message"},"parameters":{"$ref":"#/definitions/error-parameters"}}},"error-parameters":{"type":"array","description":"Error parameters list","items":{"$ref":"#/definitions/error-parameters-item"}},"error-parameters-item":{"type":"object","description":"Error parameters item","properties":{"resources":{"type":"string","description":"ACL resource"},"fieldName":{"type":"string","description":"Missing or invalid field name"},"fieldValue":{"type":"string","description":"Incorrect field value"}}},"test-module5-v2-entity-all-soap-and-rest":{"type":"object","description":"Some Data Object","properties":{"price":{"type":"integer"}},"required":["price"]}}}' + '{"swagger":"2.0","info":{"version":"","title":""},"host":"magento.host","basePath":"/rest/default","schemes":["http://"],"tags":[{"name":"testModule5AllSoapAndRestV2","description":"AllSoapAndRestInterface"}],"paths":{"/V1/testModule5":{"get":{"tags":["testModule5AllSoapAndRestV2"],"description":"Retrieve existing item.","operationId":"operationNameGet","consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}}},"definitions":{"error-response":{"type":"object","properties":{"message":{"type":"string","description":"Error message"},"errors":{"$ref":"#/definitions/error-errors"},"code":{"type":"integer","description":"Error code"},"parameters":{"$ref":"#/definitions/error-parameters"},"trace":{"type":"string","description":"Stack trace"}},"required":["message"]},"error-errors":{"type":"array","description":"Errors list","items":{"$ref":"#/definitions/error-errors-item"}},"error-errors-item":{"type":"object","description":"Error details","properties":{"message":{"type":"string","description":"Error message"},"parameters":{"$ref":"#/definitions/error-parameters"}}},"error-parameters":{"type":"array","description":"Error parameters list","items":{"$ref":"#/definitions/error-parameters-item"}},"error-parameters-item":{"type":"object","description":"Error parameters item","properties":{"resources":{"type":"string","description":"ACL resource"},"fieldName":{"type":"string","description":"Missing or invalid field name"},"fieldValue":{"type":"string","description":"Incorrect field value"}}},"test-module5-v2-entity-all-soap-and-rest":{"type":"object","description":"Some Data Object","properties":{"price":{"type":"integer"}},"required":["price"]}}}' // @codingStandardsIgnoreEnd ], ]; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/UrlTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/UrlTest.php index f9075a58c39ef..4cf059d4bf692 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/UrlTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/UrlTest.php @@ -42,6 +42,46 @@ public function testGetUrlInStore() $this->assertStringEndsWith('simple-product.html', $this->_model->getUrlInStore($product)); } + /** + * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoConfigFixture default_store web/unsecure/base_url http://sample.com/ + * @magentoConfigFixture default_store web/unsecure/base_link_url http://sample.com/ + * @magentoConfigFixture fixturestore_store web/unsecure/base_url http://sample-second.com/ + * @magentoConfigFixture fixturestore_store web/unsecure/base_link_url http://sample-second.com/ + * @magentoDataFixture Magento/Catalog/_files/product_simple_multistore.php + * @dataProvider getUrlsWithSecondStoreProvider + * @magentoAppArea adminhtml + */ + public function testGetUrlInStoreWithSecondStore($storeCode, $expectedProductUrl) + { + $repository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ProductRepository::class + ); + /** @var \Magento\Store\Model\Store $store */ + $store = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Store\Model\Store::class); + $store->load($storeCode, 'code'); + /** @var \Magento\Store\Model\Store $store */ + + $product = $repository->get('simple'); + + $this->assertEquals( + $expectedProductUrl, + $this->_model->getUrlInStore($product, ['_scope' => $store->getId(), '_nosid' => true]) + ); + } + + /** + * @return array + */ + public function getUrlsWithSecondStoreProvider() + { + return [ + 'case1' => ['fixturestore', 'http://sample-second.com/index.php/simple-product-one.html'], + 'case2' => ['default', 'http://sample.com/index.php/simple-product-one.html'] + ]; + } + public function testGetProductUrl() { $repository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendmailTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendmailTest.php index a075398e9cdb7..f5851a55d760a 100644 --- a/dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendmailTest.php +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendmailTest.php @@ -85,6 +85,24 @@ public function testSendActionAsGuestWithInvalidData() ); } + /** + * Share the product invisible in catalog to friend as guest customer + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoConfigFixture default_store sendfriend/email/enabled 1 + * @magentoConfigFixture default_store sendfriend/email/allow_guest 1 + * @magentoDataFixture Magento/Catalog/_files/simple_products_not_visible_individually.php + */ + public function testSendInvisibleProduct() + { + $product = $this->getInvisibleProduct(); + $this->prepareRequestData(); + + $this->dispatch('sendfriend/product/sendmail/id/' . $product->getId()); + $this->assert404NotFound(); + } + /** * @return ProductInterface */ @@ -93,6 +111,14 @@ private function getProduct() return $this->_objectManager->get(ProductRepositoryInterface::class)->get('custom-design-simple-product'); } + /** + * @return ProductInterface + */ + private function getInvisibleProduct() + { + return $this->_objectManager->get(ProductRepositoryInterface::class)->get('simple_not_visible_1'); + } + /** * Login the user *