diff --git a/.gitignore b/.gitignore index 68d38d9ca7817..8ec1104f25535 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,8 @@ atlassian* /pub/media/import/* !/pub/media/import/.htaccess /pub/media/logo/* +/pub/media/custom_options/* +!/pub/media/custom_options/.htaccess /pub/media/theme/* /pub/media/theme_customization/* !/pub/media/theme_customization/.htaccess diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index 7a05601fcd666..1d563244f1432 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -59,7 +59,7 @@ Comma-separated. - validate-per-page-value-list + validate-per-page-value-list required-entry @@ -69,7 +69,7 @@ Comma-separated. - validate-per-page-value-list + validate-per-page-value-list required-entry diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php index 8f216b64aa9b0..2fcfd2dfadabb 100644 --- a/app/code/Magento/Quote/Model/QuoteManagement.php +++ b/app/code/Magento/Quote/Model/QuoteManagement.php @@ -532,19 +532,7 @@ protected function submitQuote(QuoteEntity $quote, $orderData = []) ); $this->quoteRepository->save($quote); } catch (\Exception $e) { - if (!empty($this->addressesToSync)) { - foreach ($this->addressesToSync as $addressId) { - $this->addressRepository->deleteById($addressId); - } - } - $this->eventManager->dispatch( - 'sales_model_service_quote_submit_failure', - [ - 'order' => $order, - 'quote' => $quote, - 'exception' => $e - ] - ); + $this->rollbackAddresses($quote, $order, $e); throw $e; } return $order; @@ -611,4 +599,41 @@ protected function _prepareCustomerQuote($quote) $shipping->setIsDefaultBilling(true); } } + + /** + * Remove related to order and quote addresses and submit exception to further processing. + * + * @param Quote $quote + * @param \Magento\Sales\Api\Data\OrderInterface $order + * @param \Exception $e + * @throws \Exception + */ + private function rollbackAddresses( + QuoteEntity $quote, + \Magento\Sales\Api\Data\OrderInterface $order, + \Exception $e + ): void { + try { + if (!empty($this->addressesToSync)) { + foreach ($this->addressesToSync as $addressId) { + $this->addressRepository->deleteById($addressId); + } + } + $this->eventManager->dispatch( + 'sales_model_service_quote_submit_failure', + [ + 'order' => $order, + 'quote' => $quote, + 'exception' => $e, + ] + ); + } catch (\Exception $consecutiveException) { + $message = sprintf( + "An exception occurred on 'sales_model_service_quote_submit_failure' event: %s", + $consecutiveException->getMessage() + ); + + throw new \Exception($message, 0, $e); + } + } } diff --git a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AddTrack.php b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AddTrack.php index fa83db490e380..c417a202321b2 100644 --- a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AddTrack.php +++ b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AddTrack.php @@ -1,14 +1,27 @@ shipmentLoader = $shipmentLoader; parent::__construct($context); + + $this->shipmentLoader = $shipmentLoader; + $this->shipmentRepository = $shipmentRepository ?: ObjectManager::getInstance() + ->get(ShipmentRepositoryInterface::class); + $this->trackFactory = $trackFactory ?: ObjectManager::getInstance() + ->get(ShipmentTrackInterfaceFactory::class); + $this->serializer = $serializer ?: ObjectManager::getInstance() + ->get(SerializerInterface::class); } /** - * Add new tracking number action + * Add new tracking number action. * - * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @return ResultInterface */ public function execute() { @@ -46,28 +86,29 @@ public function execute() $carrier = $this->getRequest()->getPost('carrier'); $number = $this->getRequest()->getPost('number'); $title = $this->getRequest()->getPost('title'); + if (empty($carrier)) { - throw new \Magento\Framework\Exception\LocalizedException(__('Please specify a carrier.')); + throw new LocalizedException(__('Please specify a carrier.')); } if (empty($number)) { - throw new \Magento\Framework\Exception\LocalizedException(__('Please enter a tracking number.')); + throw new LocalizedException(__('Please enter a tracking number.')); } + $this->shipmentLoader->setOrderId($this->getRequest()->getParam('order_id')); $this->shipmentLoader->setShipmentId($this->getRequest()->getParam('shipment_id')); $this->shipmentLoader->setShipment($this->getRequest()->getParam('shipment')); $this->shipmentLoader->setTracking($this->getRequest()->getParam('tracking')); $shipment = $this->shipmentLoader->load(); if ($shipment) { - $track = $this->_objectManager->create( - \Magento\Sales\Model\Order\Shipment\Track::class - )->setNumber( + $track = $this->trackFactory->create()->setNumber( $number )->setCarrierCode( $carrier )->setTitle( $title ); - $shipment->addTrack($track)->save(); + $shipment->addTrack($track); + $this->shipmentRepository->save($shipment); $this->_view->loadLayout(); $this->_view->getPage()->getConfig()->getTitle()->prepend(__('Shipments')); @@ -78,16 +119,18 @@ public function execute() 'message' => __('We can\'t initialize shipment for adding tracking number.'), ]; } - } catch (\Magento\Framework\Exception\LocalizedException $e) { + } catch (LocalizedException $e) { $response = ['error' => true, 'message' => $e->getMessage()]; } catch (\Exception $e) { $response = ['error' => true, 'message' => __('Cannot add tracking number.')]; } - if (is_array($response)) { - $response = $this->_objectManager->get(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($response); - $this->getResponse()->representJson($response); - } else { - $this->getResponse()->setBody($response); + + if (\is_array($response)) { + $response = $this->serializer->serialize($response); + + return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setJsonData($response); } + + return $this->resultFactory->create(ResultFactory::TYPE_RAW)->setContents($response); } } diff --git a/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/AddTrackTest.php b/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/AddTrackTest.php index cf3bf68e10416..1c9cdb1fa7d5b 100644 --- a/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/AddTrackTest.php +++ b/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/AddTrackTest.php @@ -6,127 +6,162 @@ namespace Magento\Shipping\Test\Unit\Controller\Adminhtml\Order\Shipment; -use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Request\Http; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\App\ViewInterface; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\ResultInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\View\Element\BlockInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\Framework\View\Page\Config; +use Magento\Framework\View\Page\Title; +use Magento\Framework\View\Result\Page; +use Magento\Sales\Api\Data\ShipmentTrackInterfaceFactory; +use Magento\Sales\Model\Order\Shipment; +use Magento\Sales\Model\Order\Shipment\Track; +use Magento\Shipping\Controller\Adminhtml\Order\Shipment\AddTrack; +use Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader; /** - * Class AddTrackTest + * Class AddTrackTest covers AddTrack controller. * - * @package Magento\Shipping\Controller\Adminhtml\Order\Shipment * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AddTrackTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader|\PHPUnit_Framework_MockObject_MockObject + * @var ShipmentLoader|\PHPUnit_Framework_MockObject_MockObject */ - protected $shipmentLoader; + private $shipmentLoader; /** - * @var \Magento\Shipping\Controller\Adminhtml\Order\Shipment\AddTrack + * @var AddTrack */ - protected $controller; + private $controller; /** - * @var Action\Context|\PHPUnit_Framework_MockObject_MockObject + * @var Context|\PHPUnit_Framework_MockObject_MockObject */ - protected $context; + private $context; /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject + * @var Http|\PHPUnit_Framework_MockObject_MockObject */ - protected $request; + private $request; /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ResponseInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $response; + private $response; /** - * @var \Magento\Framework\ObjectManager\ObjectManager|\PHPUnit_Framework_MockObject_MockObject + * @var ViewInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $objectManager; + private $view; /** - * @var \Magento\Framework\App\ViewInterface|\PHPUnit_Framework_MockObject_MockObject + * @var Page|\PHPUnit_Framework_MockObject_MockObject */ - protected $view; + private $resultPageMock; /** - * @var \Magento\Framework\View\Result\Page|\PHPUnit_Framework_MockObject_MockObject + * @var Config|\PHPUnit_Framework_MockObject_MockObject */ - protected $resultPageMock; + private $pageConfigMock; /** - * @var \Magento\Framework\View\Page\Config|\PHPUnit_Framework_MockObject_MockObject + * @var Title|\PHPUnit_Framework_MockObject_MockObject */ - protected $pageConfigMock; + private $pageTitleMock; /** - * @var \Magento\Framework\View\Page\Title|\PHPUnit_Framework_MockObject_MockObject + * @var ShipmentTrackInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $pageTitleMock; + private $trackFactory; + /** + * @var ResultInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $rawResult; + + /** + * @inheritdoc + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); $this->shipmentLoader = $this->getMockBuilder( - \Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader::class + ShipmentLoader::class ) ->disableOriginalConstructor() ->setMethods(['setShipmentId', 'setOrderId', 'setShipment', 'setTracking', 'load']) ->getMock(); $this->context = $this->createPartialMock( - \Magento\Backend\App\Action\Context::class, + Context::class, [ 'getRequest', 'getResponse', 'getRedirect', 'getObjectManager', 'getTitle', - 'getView' + 'getView', + 'getResultFactory' ] ); $this->response = $this->createPartialMock( - \Magento\Framework\App\ResponseInterface::class, + ResponseInterface::class, ['setRedirect', 'sendResponse', 'setBody'] ); - $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) + $this->request = $this->getMockBuilder(Http::class) ->disableOriginalConstructor()->getMock(); - $this->objectManager = $this->createPartialMock( - \Magento\Framework\ObjectManager\ObjectManager::class, - ['create', 'get'] - ); - $this->view = $this->createMock(\Magento\Framework\App\ViewInterface::class); - $this->resultPageMock = $this->getMockBuilder(\Magento\Framework\View\Result\Page::class) + $this->view = $this->createMock(ViewInterface::class); + $this->resultPageMock = $this->getMockBuilder(Page::class) ->disableOriginalConstructor() ->getMock(); - $this->pageConfigMock = $this->getMockBuilder(\Magento\Framework\View\Page\Config::class) + $this->pageConfigMock = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); - $this->pageTitleMock = $this->getMockBuilder(\Magento\Framework\View\Page\Title::class) + $this->pageTitleMock = $this->getMockBuilder(Title::class) ->disableOriginalConstructor() ->getMock(); + $this->trackFactory = $this->getMockBuilder(ShipmentTrackInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMockForAbstractClass(); + $this->rawResult = $this->getMockBuilder(ResultInterface::class) + ->disableOriginalConstructor() + ->setMethods(['setContents']) + ->getMockForAbstractClass(); + $resultFactory = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMockForAbstractClass(); $this->context->expects($this->once()) ->method('getRequest') ->will($this->returnValue($this->request)); $this->context->expects($this->once()) ->method('getResponse') ->will($this->returnValue($this->response)); - $this->context->expects($this->once()) - ->method('getObjectManager') - ->will($this->returnValue($this->objectManager)); $this->context->expects($this->once()) ->method('getView') ->will($this->returnValue($this->view)); + $resultFactory->expects($this->once()) + ->method('create') + ->willReturn($this->rawResult); + $this->context->expects($this->once()) + ->method('getResultFactory') + ->willReturn($resultFactory); $this->controller = $objectManagerHelper->getObject( - \Magento\Shipping\Controller\Adminhtml\Order\Shipment\AddTrack::class, + AddTrack::class, [ 'context' => $this->context, 'shipmentLoader' => $this->shipmentLoader, 'request' => $this->request, 'response' => $this->response, - 'view' => $this->view + 'view' => $this->view, + 'trackFactory' => $this->trackFactory, ] ); } @@ -144,7 +179,7 @@ public function testExecute() $tracking = []; $shipmentData = ['items' => [], 'send_email' => '']; $shipment = $this->createPartialMock( - \Magento\Sales\Model\Order\Shipment::class, + Shipment::class, ['addTrack', '__wakeup', 'save'] ); $this->request->expects($this->any()) @@ -152,8 +187,10 @@ public function testExecute() ->will( $this->returnValueMap( [ - ['order_id', null, $orderId], ['shipment_id', null, $shipmentId], - ['shipment', null, $shipmentData], ['tracking', null, $tracking], + ['order_id', null, $orderId], + ['shipment_id', null, $shipmentId], + ['shipment', null, $shipmentData], + ['tracking', null, $tracking], ] ) ); @@ -183,14 +220,13 @@ public function testExecute() $this->shipmentLoader->expects($this->once()) ->method('load') ->will($this->returnValue($shipment)); - $track = $this->getMockBuilder(\Magento\Sales\Model\Order\Shipment\Track::class) + $track = $this->getMockBuilder(Track::class) ->disableOriginalConstructor() ->setMethods(['__wakeup', 'setNumber', 'setCarrierCode', 'setTitle']) ->getMock(); - $this->objectManager->expects($this->atLeastOnce()) + $this->trackFactory->expects($this->once()) ->method('create') - ->with(\Magento\Sales\Model\Order\Shipment\Track::class) - ->will($this->returnValue($track)); + ->willReturn($track); $track->expects($this->once()) ->method('setNumber') ->with($number) @@ -206,8 +242,8 @@ public function testExecute() $this->view->expects($this->once()) ->method('loadLayout') ->will($this->returnSelf()); - $layout = $this->createMock(\Magento\Framework\View\LayoutInterface::class); - $menuBlock = $this->createPartialMock(\Magento\Framework\View\Element\BlockInterface::class, ['toHtml']); + $layout = $this->createMock(LayoutInterface::class); + $menuBlock = $this->createPartialMock(BlockInterface::class, ['toHtml']); $html = 'html string'; $this->view->expects($this->once()) ->method('getLayout') @@ -235,9 +271,10 @@ public function testExecute() $this->pageConfigMock->expects($this->any()) ->method('getTitle') ->willReturn($this->pageTitleMock); - $this->response->expects($this->once()) - ->method('setBody') - ->with($html); - $this->assertNull($this->controller->execute()); + $this->rawResult->expects($this->once()) + ->method('setContents') + ->with($html) + ->willReturnSelf(); + $this->assertInstanceOf(ResultInterface::class, $this->controller->execute()); } } diff --git a/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js b/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js index 87b5b2f5fe8fe..038907c21224d 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js @@ -79,7 +79,7 @@ define([ /** * Extracts and parses data stored in localStorage by the - * key specified in 'root' varaible. + * key specified in 'root' variable. * * @returns {Object} */ diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js index d765f842a0895..97b47f77beeab 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js @@ -920,12 +920,12 @@ define([ ], 'validate-per-page-value-list': [ function (value) { - var isValid = utils.isEmpty(value), + var isValid = true, values = value.split(','), i; - if (isValid) { - return true; + if (utils.isEmpty(value)) { + return isValid; } for (i = 0; i < values.length; i++) { diff --git a/lib/web/mage/gallery/gallery.js b/lib/web/mage/gallery/gallery.js index 15c3d01cf2be3..2ed3f8b0d620c 100644 --- a/lib/web/mage/gallery/gallery.js +++ b/lib/web/mage/gallery/gallery.js @@ -141,7 +141,7 @@ define([ this.setupBreakpoints(); this.initFullscreenSettings(); - this.settings.$element.on('mouseup', '.fotorama__stage__frame', function () { + this.settings.$element.on('mousedown', '.fotorama__stage__frame', function () { if ( !$(this).parents('.fotorama__shadows--left, .fotorama__shadows--right').length && !$(this).hasClass('fotorama-video-container') diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index dfa35473176b9..a57191fd4aff4 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -1431,10 +1431,14 @@ ], 'validate-per-page-value-list': [ function (v) { - var isValid = !$.mage.isEmpty(v), + var isValid = true, values = v.split(','), i; + if ($.mage.isEmpty(v)) { + return isValid; + } + for (i = 0; i < values.length; i++) { if (!/^[0-9]+$/.test(values[i])) { isValid = false;