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;