+ Magento supports PHP 7.1.3 or later. Please read
+
Magento System Requirements .
HTML;
diff --git a/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox.php b/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox.php
index a92eebfaec51..40b089d94713 100644
--- a/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox.php
+++ b/app/code/Magento/AdminNotification/Model/ResourceModel/Inbox.php
@@ -6,6 +6,8 @@
namespace Magento\AdminNotification\Model\ResourceModel;
/**
+ * Inbox resource model
+ *
* @api
* @since 100.0.2
*/
@@ -77,8 +79,7 @@ public function getNoticeStatus(\Magento\AdminNotification\Model\Inbox $object)
'is_read=?',
0
);
- $return = $connection->fetchPairs($select);
- return $return;
+ return $connection->fetchPairs($select);
}
/**
diff --git a/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Error.php b/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Error.php
index 38477c015cad..c08b13373aa8 100644
--- a/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Error.php
+++ b/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Error.php
@@ -7,6 +7,8 @@
namespace Magento\AdminNotification\Model\System\Message\Media\Synchronization;
/**
+ * Media synchronization error message class.
+ *
* @api
* @since 100.0.2
*/
@@ -27,7 +29,7 @@ class Error extends \Magento\AdminNotification\Model\System\Message\Media\Abstra
protected function _shouldBeDisplayed()
{
$data = $this->_syncFlag->getFlagData();
- return isset($data['has_errors']) && true == $data['has_errors'];
+ return !empty($data['has_errors']);
}
/**
diff --git a/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Success.php b/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Success.php
index 81bea14099e9..ed882a077673 100644
--- a/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Success.php
+++ b/app/code/Magento/AdminNotification/Model/System/Message/Media/Synchronization/Success.php
@@ -6,6 +6,8 @@
namespace Magento\AdminNotification\Model\System\Message\Media\Synchronization;
/**
+ * Media synchronization success message class.
+ *
* @api
* @since 100.0.2
*/
@@ -27,8 +29,8 @@ protected function _shouldBeDisplayed()
{
$state = $this->_syncFlag->getState();
$data = $this->_syncFlag->getFlagData();
- $hasErrors = isset($data['has_errors']) && true == $data['has_errors'] ? true : false;
- return false == $hasErrors && \Magento\MediaStorage\Model\File\Storage\Flag::STATE_FINISHED == $state;
+ $hasErrors = !empty($data['has_errors']);
+ return !$hasErrors && \Magento\MediaStorage\Model\File\Storage\Flag::STATE_FINISHED == $state;
}
/**
diff --git a/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php b/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php
index 14b80c6814ba..3af168886a44 100644
--- a/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php
+++ b/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php
@@ -76,7 +76,7 @@ public function getColumns(SelectBuilder $selectBuilder, $entityConfig)
$columnName = $this->nameResolver->getName($attributeData);
if (isset($attributeData['function'])) {
$prefix = '';
- if (isset($attributeData['distinct']) && $attributeData['distinct'] == true) {
+ if (!empty($attributeData['distinct'])) {
$prefix = ' DISTINCT ';
}
$expression = new ColumnValueExpression(
diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml
index 624aa952fbce..bb682c446801 100644
--- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml
+++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php
index 47747027ed70..cf00556cfe59 100644
--- a/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php
+++ b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php
@@ -191,7 +191,7 @@ public function testPrepareExportData($isArchiveSourceDirectory)
->with(
$archiveSource,
$archiveAbsolutePath,
- $isArchiveSourceDirectory ? true : false
+ $isArchiveSourceDirectory
);
$fileContent = 'Some text';
@@ -222,7 +222,7 @@ public function prepareExportDataDataProvider()
{
return [
'Data source for archive is directory' => [true],
- 'Data source for archive doesn\'t directory' => [false],
+ 'Data source for archive isn\'t directory' => [false],
];
}
diff --git a/app/code/Magento/Authorizenet/Block/Adminhtml/Order/View/Info/PaymentDetails.php b/app/code/Magento/Authorizenet/Block/Adminhtml/Order/View/Info/PaymentDetails.php
new file mode 100644
index 000000000000..fb9c74d2f0ab
--- /dev/null
+++ b/app/code/Magento/Authorizenet/Block/Adminhtml/Order/View/Info/PaymentDetails.php
@@ -0,0 +1,28 @@
+setIsTransactionPending(true)
->setIsFraudDetected(true);
}
+
+ $additionalInformationKeys = explode(',', $this->getValue('paymentInfoKeys'));
+ foreach ($additionalInformationKeys as $paymentInfoKey) {
+ $paymentInfoValue = $response->getDataByKey($paymentInfoKey);
+ if ($paymentInfoValue !== null) {
+ $payment->setAdditionalInformation($paymentInfoKey, $paymentInfoValue);
+ }
+ }
}
/**
@@ -682,6 +689,7 @@ protected function matchAmount($amount)
/**
* Operate with order using information from Authorize.net.
+ *
* Authorize order or authorize and capture it.
*
* @param \Magento\Sales\Model\Order $order
@@ -699,6 +707,7 @@ protected function processOrder(\Magento\Sales\Model\Order $order)
//decline the order (in case of wrong response code) but don't return money to customer.
$message = $e->getMessage();
$this->declineOrder($order, $message, false);
+
throw $e;
}
@@ -769,7 +778,7 @@ protected function processPaymentFraudStatus(\Magento\Sales\Model\Order\Payment
}
/**
- * Add status comment
+ * Add status comment to history
*
* @param \Magento\Sales\Model\Order\Payment $payment
* @return $this
@@ -824,6 +833,7 @@ protected function declineOrder(\Magento\Sales\Model\Order $order, $message = ''
->void($response);
}
$order->registerCancellation($message)->save();
+ $this->_eventManager->dispatch('order_cancel_after', ['order' => $order ]);
} catch (\Exception $e) {
//quiet decline
$this->getPsrLogger()->critical($e);
@@ -858,7 +868,7 @@ public function getConfigInterface()
* Getter for specified value according to set payment method code
*
* @param mixed $key
- * @param null $storeId
+ * @param mixed $storeId
* @return mixed
*/
public function getValue($key, $storeId = null)
@@ -918,10 +928,12 @@ public function fetchTransactionInfo(\Magento\Payment\Model\InfoInterface $payme
$payment->setIsTransactionDenied(true);
}
$this->addStatusCommentOnUpdate($payment, $response, $transactionId);
- return [];
+ return $response->getData();
}
/**
+ * Add status comment on update
+ *
* @param \Magento\Sales\Model\Order\Payment $payment
* @param \Magento\Framework\DataObject $response
* @param string $transactionId
@@ -996,8 +1008,9 @@ protected function getTransactionResponse($transactionId)
}
/**
- * @return \Psr\Log\LoggerInterface
+ * Get psr logger.
*
+ * @return \Psr\Log\LoggerInterface
* @deprecated 100.1.0
*/
private function getPsrLogger()
@@ -1038,7 +1051,9 @@ private function getOrderIncrementId(): string
}
/**
- * Checks if filter action is Report Only. Transactions that trigger this filter are processed as normal,
+ * Checks if filter action is Report Only.
+ *
+ * Transactions that trigger this filter are processed as normal,
* but are also reported in the Merchant Interface as triggering this filter.
*
* @param string $fdsFilterAction
diff --git a/app/code/Magento/Authorizenet/etc/config.xml b/app/code/Magento/Authorizenet/etc/config.xml
index eacf77cda1e7..3a192646b6f7 100644
--- a/app/code/Magento/Authorizenet/etc/config.xml
+++ b/app/code/Magento/Authorizenet/etc/config.xml
@@ -32,6 +32,7 @@
https://secure.authorize.net/gateway/transact.dll
https://apitest.authorize.net/xml/v1/request.api
https://api2.authorize.net/xml/v1/request.api
+ x_card_type,x_account_number,x_avs_code,x_auth_code,x_response_reason_text,x_cvv2_resp_code
diff --git a/app/code/Magento/Authorizenet/etc/di.xml b/app/code/Magento/Authorizenet/etc/di.xml
index 4beb2456be11..69d24019f2fb 100644
--- a/app/code/Magento/Authorizenet/etc/di.xml
+++ b/app/code/Magento/Authorizenet/etc/di.xml
@@ -35,4 +35,9 @@
+
+
+ Magento\Authorizenet\Model\Directpost
+
+
diff --git a/app/code/Magento/Authorizenet/i18n/en_US.csv b/app/code/Magento/Authorizenet/i18n/en_US.csv
index bb59afffff2c..6228d5102b13 100644
--- a/app/code/Magento/Authorizenet/i18n/en_US.csv
+++ b/app/code/Magento/Authorizenet/i18n/en_US.csv
@@ -67,3 +67,9 @@ Debug,Debug
"Minimum Order Total","Minimum Order Total"
"Maximum Order Total","Maximum Order Total"
"Sort Order","Sort Order"
+"x_card_type","Credit Card Type"
+"x_account_number", "Credit Card Number"
+"x_avs_code","AVS Response Code"
+"x_auth_code","Processor Authentication Code"
+"x_response_reason_text","Processor Response Text"
+"x_cvv2_resp_code","CVV2 Response Code"
diff --git a/app/code/Magento/Authorizenet/view/adminhtml/templates/order/view/info/fraud_details.phtml b/app/code/Magento/Authorizenet/view/adminhtml/templates/order/view/info/fraud_details.phtml
index 60fec263352f..ac91fa30bfbe 100644
--- a/app/code/Magento/Authorizenet/view/adminhtml/templates/order/view/info/fraud_details.phtml
+++ b/app/code/Magento/Authorizenet/view/adminhtml/templates/order/view/info/fraud_details.phtml
@@ -44,8 +44,8 @@ $fraudDetails = $payment->getAdditionalInformation('fraud_details');
- = $block->escapeHtml(__('Fraud Filters')) ?>:
-
+ = $block->escapeHtml(__('Fraud Filters')) ?>:
+
= $block->escapeHtml($filter['name']) ?>:
= $block->escapeHtml($filter['action']) ?>
diff --git a/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js b/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js
index 2b57e5cc2fb0..8c4c90bf111d 100644
--- a/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js
+++ b/app/code/Magento/Authorizenet/view/frontend/requirejs-config.js
@@ -6,7 +6,8 @@
var config = {
map: {
'*': {
- transparent: 'Magento_Payment/js/transparent'
+ transparent: 'Magento_Payment/js/transparent',
+ 'Magento_Payment/transparent': 'Magento_Payment/js/transparent'
}
}
};
diff --git a/app/code/Magento/Backend/Block/Page/Footer.php b/app/code/Magento/Backend/Block/Page/Footer.php
index 3d1570e5ddfe..e0c173a4cbfe 100644
--- a/app/code/Magento/Backend/Block/Page/Footer.php
+++ b/app/code/Magento/Backend/Block/Page/Footer.php
@@ -40,7 +40,7 @@ public function __construct(
}
/**
- * @return void
+ * @inheritdoc
*/
protected function _construct()
{
@@ -57,4 +57,12 @@ public function getMagentoVersion()
{
return $this->productMetadata->getVersion();
}
+
+ /**
+ * @inheritdoc
+ */
+ protected function getCacheLifetime()
+ {
+ return 3600 * 24 * 10;
+ }
}
diff --git a/app/code/Magento/Backend/Block/System/Store/Delete/Form.php b/app/code/Magento/Backend/Block/System/Store/Delete/Form.php
index e479e8f560da..47a156c16ce3 100644
--- a/app/code/Magento/Backend/Block/System/Store/Delete/Form.php
+++ b/app/code/Magento/Backend/Block/System/Store/Delete/Form.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Backend\Block\System\Store\Delete;
+use Magento\Backup\Helper\Data as BackupHelper;
+use Magento\Framework\App\ObjectManager;
+
/**
* Adminhtml cms block edit form
*
@@ -12,6 +15,25 @@
*/
class Form extends \Magento\Backend\Block\Widget\Form\Generic
{
+ /**
+ * @var BackupHelper
+ */
+ private $backup;
+
+ /**
+ * @inheritDoc
+ */
+ public function __construct(
+ \Magento\Backend\Block\Template\Context $context,
+ \Magento\Framework\Registry $registry,
+ \Magento\Framework\Data\FormFactory $formFactory,
+ array $data = [],
+ ?BackupHelper $backup = null
+ ) {
+ parent::__construct($context, $registry, $formFactory, $data);
+ $this->backup = $backup ?? ObjectManager::getInstance()->get(BackupHelper::class);
+ }
+
/**
* Init form
*
@@ -25,7 +47,7 @@ protected function _construct()
}
/**
- * {@inheritdoc}
+ * @inheritDoc
*/
protected function _prepareForm()
{
@@ -45,6 +67,12 @@ protected function _prepareForm()
$fieldset->addField('item_id', 'hidden', ['name' => 'item_id', 'value' => $dataObject->getId()]);
+ $backupOptions = ['0' => __('No')];
+ $backupSelected = '0';
+ if ($this->backup->isEnabled()) {
+ $backupOptions['1'] = __('Yes');
+ $backupSelected = '1';
+ }
$fieldset->addField(
'create_backup',
'select',
@@ -52,8 +80,8 @@ protected function _prepareForm()
'label' => __('Create DB Backup'),
'title' => __('Create DB Backup'),
'name' => 'create_backup',
- 'options' => ['1' => __('Yes'), '0' => __('No')],
- 'value' => '1'
+ 'options' => $backupOptions,
+ 'value' => $backupSelected
]
);
diff --git a/app/code/Magento/Backend/Block/Widget/Form.php b/app/code/Magento/Backend/Block/Widget/Form.php
index 59b5cc060cc0..38d5d90a22d1 100644
--- a/app/code/Magento/Backend/Block/Widget/Form.php
+++ b/app/code/Magento/Backend/Block/Widget/Form.php
@@ -59,7 +59,6 @@ protected function _construct()
parent::_construct();
$this->setDestElementId('edit_form');
- $this->setShowGlobalIcon(false);
}
/**
diff --git a/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php b/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php
index eff49c3b75ab..d599d5fbad5e 100644
--- a/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php
+++ b/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php
@@ -4,14 +4,13 @@
* See COPYING.txt for license details.
*/
+namespace Magento\Backend\Block\Widget\Form\Element;
+
/**
* Form element dependencies mapper
* Assumes that one element may depend on other element values.
* Will toggle as "enabled" only if all elements it depends from toggle as true.
- */
-namespace Magento\Backend\Block\Widget\Form\Element;
-
-/**
+ *
* @api
* @since 100.0.2
*/
@@ -117,6 +116,7 @@ public function addConfigOptions(array $options)
/**
* HTML output getter
+ *
* @return string
*/
protected function _toHtml()
@@ -139,7 +139,8 @@ protected function _toHtml()
}
/**
- * Field dependences JSON map generator
+ * Field dependencies JSON map generator
+ *
* @return string
*/
protected function _getDependsJson()
diff --git a/app/code/Magento/Backend/Block/Widget/Grid.php b/app/code/Magento/Backend/Block/Widget/Grid.php
index 72ab5a265d80..66298d23389f 100644
--- a/app/code/Magento/Backend/Block/Widget/Grid.php
+++ b/app/code/Magento/Backend/Block/Widget/Grid.php
@@ -12,7 +12,7 @@
* @api
* @deprecated 100.2.0 in favour of UI component implementation
* @method string getRowClickCallback() getRowClickCallback()
- * @method \Magento\Backend\Block\Widget\Grid setRowClickCallback() setRowClickCallback(string $value)
+ * @method \Magento\Backend\Block\Widget\Grid setRowClickCallback(string $value)
* @SuppressWarnings(PHPMD.TooManyFields)
* @since 100.0.2
*/
@@ -150,7 +150,10 @@ public function __construct(
}
/**
+ * Internal constructor, that is called from real constructor
+ *
* @return void
+ *
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
protected function _construct()
@@ -709,6 +712,7 @@ public function getGridUrl()
/**
* Grid url getter
+ *
* Version of getGridUrl() but with parameters
*
* @param array $params url parameters
diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php
index 185b1116b8f6..9890a10a4ceb 100644
--- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php
+++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php
@@ -7,6 +7,7 @@
namespace Magento\Backend\Block\Widget\Grid\Massaction;
use Magento\Backend\Block\Widget\Grid\Massaction\VisibilityCheckerInterface as VisibilityChecker;
+use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\DataObject;
/**
@@ -52,7 +53,7 @@ public function __construct(
}
/**
- * @return void
+ * @inheritdoc
*/
protected function _construct()
{
@@ -217,6 +218,7 @@ public function getGridJsObjectName()
* Retrieve JSON string of selected checkboxes
*
* @return string
+ * @SuppressWarnings(PHPMD.RequestAwareBlockMethod)
*/
public function getSelectedJson()
{
@@ -231,6 +233,7 @@ public function getSelectedJson()
* Retrieve array of selected checkboxes
*
* @return string[]
+ * @SuppressWarnings(PHPMD.RequestAwareBlockMethod)
*/
public function getSelected()
{
@@ -252,6 +255,8 @@ public function getApplyButtonHtml()
}
/**
+ * Get mass action javascript code.
+ *
* @return string
*/
public function getJavaScript()
@@ -268,6 +273,8 @@ public function getJavaScript()
}
/**
+ * Get grid ids in JSON format.
+ *
* @return string
*/
public function getGridIdsJson()
@@ -284,6 +291,11 @@ public function getGridIdsJson()
$massActionIdField = $this->getParentBlock()->getMassactionIdField();
}
+ if ($allIdsCollection instanceof AbstractDb) {
+ $allIdsCollection->getSelect()->limit();
+ $allIdsCollection->clear();
+ }
+
$gridIds = $allIdsCollection->setPageSize(0)->getColumnValues($massActionIdField);
if (!empty($gridIds)) {
return join(",", $gridIds);
@@ -292,6 +304,8 @@ public function getGridIdsJson()
}
/**
+ * Get Html id.
+ *
* @return string
*/
public function getHtmlId()
diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php
index 0beeb5168b6d..a9be14b77b29 100644
--- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php
+++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php
@@ -14,6 +14,7 @@
* Store controller
*
* @author Magento Core Team
+ * @SuppressWarnings(PHPMD.AllPurposeAction)
*/
abstract class Store extends Action
{
@@ -86,6 +87,8 @@ protected function createPage()
* Backup database
*
* @return bool
+ *
+ * @deprecated Backup module is to be removed.
*/
protected function _backupDatabase()
{
diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml
index a7ef237a232b..1070bc409962 100644
--- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml
+++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml
@@ -13,10 +13,9 @@
-
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml b/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml
index 8e30afdf51f7..b4bc42b95d0a 100644
--- a/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml
+++ b/app/code/Magento/Backend/view/adminhtml/templates/system/cache/additional.phtml
@@ -11,10 +11,10 @@ $permissions = $block->getData('permissions');
?>
hasAccessToAdditionalActions()): ?>
+
+ = $block->escapeHtml(__('Additional Cache Management')); ?>
+
hasAccessToFlushCatalogImages()): ?>
-
- = $block->escapeHtml(__('Additional Cache Management')); ?>
-
= $block->escapeHtml(__('Flush Catalog Images Cache')); ?>
diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index.php b/app/code/Magento/Backup/Controller/Adminhtml/Index.php
index 94dca327195f..0edeb5565f28 100644
--- a/app/code/Magento/Backup/Controller/Adminhtml/Index.php
+++ b/app/code/Magento/Backup/Controller/Adminhtml/Index.php
@@ -7,6 +7,8 @@
use Magento\Backend\App\Action;
use Magento\Framework\App\Action\HttpGetActionInterface;
+use Magento\Backup\Helper\Data as Helper;
+use Magento\Framework\App\ObjectManager;
/**
* Backup admin controller
@@ -14,6 +16,7 @@
* @author Magento Core Team
* @api
* @since 100.0.2
+ * @SuppressWarnings(PHPMD.AllPurposeAction)
*/
abstract class Index extends Action implements HttpGetActionInterface
{
@@ -51,6 +54,11 @@ abstract class Index extends Action implements HttpGetActionInterface
*/
protected $maintenanceMode;
+ /**
+ * @var Helper
+ */
+ private $helper;
+
/**
* @param \Magento\Backend\App\Action\Context $context
* @param \Magento\Framework\Registry $coreRegistry
@@ -58,6 +66,7 @@ abstract class Index extends Action implements HttpGetActionInterface
* @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
* @param \Magento\Backup\Model\BackupFactory $backupModelFactory
* @param \Magento\Framework\App\MaintenanceMode $maintenanceMode
+ * @param Helper|null $helper
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
@@ -65,13 +74,27 @@ public function __construct(
\Magento\Framework\Backup\Factory $backupFactory,
\Magento\Framework\App\Response\Http\FileFactory $fileFactory,
\Magento\Backup\Model\BackupFactory $backupModelFactory,
- \Magento\Framework\App\MaintenanceMode $maintenanceMode
+ \Magento\Framework\App\MaintenanceMode $maintenanceMode,
+ ?Helper $helper = null
) {
$this->_coreRegistry = $coreRegistry;
$this->_backupFactory = $backupFactory;
$this->_fileFactory = $fileFactory;
$this->_backupModelFactory = $backupModelFactory;
$this->maintenanceMode = $maintenanceMode;
+ $this->helper = $helper ?? ObjectManager::getInstance()->get(Helper::class);
parent::__construct($context);
}
+
+ /**
+ * @inheritDoc
+ */
+ public function dispatch(\Magento\Framework\App\RequestInterface $request)
+ {
+ if (!$this->helper->isEnabled()) {
+ return $this->_redirect('*/*/disabled');
+ }
+
+ return parent::dispatch($request);
+ }
}
diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Disabled.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Disabled.php
new file mode 100644
index 000000000000..f6fe430ae083
--- /dev/null
+++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Disabled.php
@@ -0,0 +1,48 @@
+pageFactory = $pageFactory;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function execute()
+ {
+ return $this->pageFactory->create();
+ }
+}
diff --git a/app/code/Magento/Backup/Cron/SystemBackup.php b/app/code/Magento/Backup/Cron/SystemBackup.php
index 750262ab1c14..9502377a39d0 100644
--- a/app/code/Magento/Backup/Cron/SystemBackup.php
+++ b/app/code/Magento/Backup/Cron/SystemBackup.php
@@ -8,6 +8,9 @@
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Store\Model\ScopeInterface;
+/**
+ * Performs scheduled backup.
+ */
class SystemBackup
{
const XML_PATH_BACKUP_ENABLED = 'system/backup/enabled';
@@ -101,6 +104,10 @@ public function __construct(
*/
public function execute()
{
+ if (!$this->_backupData->isEnabled()) {
+ return $this;
+ }
+
if (!$this->_scopeConfig->isSetFlag(self::XML_PATH_BACKUP_ENABLED, ScopeInterface::SCOPE_STORE)) {
return $this;
}
diff --git a/app/code/Magento/Backup/Helper/Data.php b/app/code/Magento/Backup/Helper/Data.php
index b0bc292ffe92..c6df6a736685 100644
--- a/app/code/Magento/Backup/Helper/Data.php
+++ b/app/code/Magento/Backup/Helper/Data.php
@@ -3,6 +3,9 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Backup\Helper;
use Magento\Framework\App\Filesystem\DirectoryList;
@@ -285,4 +288,14 @@ public function extractDataFromFilename($filename)
return $result;
}
+
+ /**
+ * Is backup functionality enabled.
+ *
+ * @return bool
+ */
+ public function isEnabled(): bool
+ {
+ return $this->scopeConfig->isSetFlag('system/backup/functionality_enabled');
+ }
}
diff --git a/app/code/Magento/Backup/Model/Db.php b/app/code/Magento/Backup/Model/Db.php
index 8fbd5da1c984..bc458a0a8e4b 100644
--- a/app/code/Magento/Backup/Model/Db.php
+++ b/app/code/Magento/Backup/Model/Db.php
@@ -5,11 +5,16 @@
*/
namespace Magento\Backup\Model;
+use Magento\Backup\Helper\Data as Helper;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Exception\RuntimeException;
+
/**
* Database backup model
*
* @api
* @since 100.0.2
+ * @deprecated Backup module is to be removed.
*/
class Db implements \Magento\Framework\Backup\Db\BackupDbInterface
{
@@ -33,16 +38,24 @@ class Db implements \Magento\Framework\Backup\Db\BackupDbInterface
*/
protected $_resource = null;
+ /**
+ * @var Helper
+ */
+ private $helper;
+
/**
* @param \Magento\Backup\Model\ResourceModel\Db $resourceDb
* @param \Magento\Framework\App\ResourceConnection $resource
+ * @param Helper|null $helper
*/
public function __construct(
\Magento\Backup\Model\ResourceModel\Db $resourceDb,
- \Magento\Framework\App\ResourceConnection $resource
+ \Magento\Framework\App\ResourceConnection $resource,
+ ?Helper $helper = null
) {
$this->_resourceDb = $resourceDb;
$this->_resource = $resource;
+ $this->helper = $helper ?? ObjectManager::getInstance()->get(Helper::class);
}
/**
@@ -63,6 +76,8 @@ public function getResource()
}
/**
+ * Tables list.
+ *
* @return array
*/
public function getTables()
@@ -71,6 +86,8 @@ public function getTables()
}
/**
+ * Command to recreate given table.
+ *
* @param string $tableName
* @param bool $addDropIfExists
* @return string
@@ -81,6 +98,8 @@ public function getTableCreateScript($tableName, $addDropIfExists = false)
}
/**
+ * Generate table's data dump.
+ *
* @param string $tableName
* @return string
*/
@@ -90,6 +109,8 @@ public function getTableDataDump($tableName)
}
/**
+ * Header for dumps.
+ *
* @return string
*/
public function getHeader()
@@ -98,6 +119,8 @@ public function getHeader()
}
/**
+ * Footer for dumps.
+ *
* @return string
*/
public function getFooter()
@@ -106,6 +129,8 @@ public function getFooter()
}
/**
+ * Get backup SQL.
+ *
* @return string
*/
public function renderSql()
@@ -124,13 +149,14 @@ public function renderSql()
}
/**
- * Create backup and stream write to adapter
- *
- * @param \Magento\Framework\Backup\Db\BackupInterface $backup
- * @return $this
+ * @inheritDoc
*/
public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backup)
{
+ if (!$this->helper->isEnabled()) {
+ throw new RuntimeException(__('Backup functionality is disabled'));
+ }
+
$backup->open(true);
$this->getResource()->beginTransaction();
@@ -179,8 +205,6 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu
$this->getResource()->commitTransaction();
$backup->close();
-
- return $this;
}
/**
diff --git a/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml b/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml
index 496bb7934309..26f8817c0a1b 100644
--- a/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml
+++ b/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml
@@ -18,8 +18,7 @@
-
-
+
diff --git a/app/code/Magento/Backup/Test/Unit/Cron/SystemBackupTest.php b/app/code/Magento/Backup/Test/Unit/Cron/SystemBackupTest.php
index b7dfb30c0a1b..56a7ef42a0bc 100644
--- a/app/code/Magento/Backup/Test/Unit/Cron/SystemBackupTest.php
+++ b/app/code/Magento/Backup/Test/Unit/Cron/SystemBackupTest.php
@@ -3,134 +3,44 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Backup\Test\Unit\Cron;
+use Magento\Backup\Cron\SystemBackup;
+use PHPUnit\Framework\TestCase;
+use Magento\Backup\Helper\Data as Helper;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-class SystemBackupTest extends \PHPUnit\Framework\TestCase
+class SystemBackupTest extends TestCase
{
/**
- * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
- */
- private $objectManager;
-
- /**
- * @var \Magento\Backup\Cron\SystemBackup
- */
- private $systemBackup;
-
- /**
- * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject
- */
- private $scopeConfigMock;
-
- /**
- * @var \Magento\Backup\Helper\Data|\PHPUnit_Framework_MockObject_MockObject
- */
- private $backupDataMock;
-
- /**
- * @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject
- */
- private $registryMock;
-
- /**
- * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var Helper|\PHPUnit_Framework_MockObject_MockObject
*/
- private $loggerMock;
+ private $helperMock;
/**
- * Filesystem facade
- *
- * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject
+ * @var SystemBackup
*/
- private $filesystemMock;
+ private $cron;
/**
- * @var \Magento\Framework\Backup\Factory|\PHPUnit_Framework_MockObject_MockObject
+ * @inheritDoc
*/
- private $backupFactoryMock;
-
- /**
- * @var \Magento\Framework\App\MaintenanceMode|\PHPUnit_Framework_MockObject_MockObject
- */
- private $maintenanceModeMock;
-
- /**
- * @var \Magento\Framework\Backup\Db|\PHPUnit_Framework_MockObject_MockObject
- */
- private $backupDbMock;
-
- /**
- * @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject
- */
- private $objectManagerMock;
-
protected function setUp()
{
- $this->objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class)
- ->getMock();
- $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)
- ->getMock();
- $this->backupDataMock = $this->getMockBuilder(\Magento\Backup\Helper\Data::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->registryMock = $this->getMockBuilder(\Magento\Framework\Registry::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
- ->getMock();
- $this->filesystemMock = $this->getMockBuilder(\Magento\Framework\Filesystem::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->backupFactoryMock = $this->getMockBuilder(\Magento\Framework\Backup\Factory::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->maintenanceModeMock = $this->getMockBuilder(\Magento\Framework\App\MaintenanceMode::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->backupDbMock = $this->getMockBuilder(\Magento\Framework\Backup\Db::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->backupDbMock->expects($this->any())->method('setBackupExtension')->willReturnSelf();
- $this->backupDbMock->expects($this->any())->method('setTime')->willReturnSelf();
- $this->backupDbMock->expects($this->any())->method('setBackupsDir')->willReturnSelf();
-
- $this->objectManager = new ObjectManager($this);
- $this->systemBackup = $this->objectManager->getObject(
- \Magento\Backup\Cron\SystemBackup::class,
- [
- 'backupData' => $this->backupDataMock,
- 'coreRegistry' => $this->registryMock,
- 'logger' => $this->loggerMock,
- 'scopeConfig' => $this->scopeConfigMock,
- 'filesystem' => $this->filesystemMock,
- 'backupFactory' => $this->backupFactoryMock,
- 'maintenanceMode' => $this->maintenanceModeMock,
- ]
- );
+ $objectManager = new ObjectManager($this);
+ $this->helperMock = $this->getMockBuilder(Helper::class)->disableOriginalConstructor()->getMock();
+ $this->cron = $objectManager->getObject(SystemBackup::class, ['backupData' => $this->helperMock]);
}
/**
- * @expectedException \Exception
+ * Test that cron doesn't do anything if backups are disabled.
*/
- public function testExecuteThrowsException()
+ public function testDisabled()
{
- $type = 'db';
- $this->scopeConfigMock->expects($this->any())->method('isSetFlag')->willReturn(true);
-
- $this->scopeConfigMock->expects($this->once())->method('getValue')
- ->with('system/backup/type', 'store')
- ->willReturn($type);
-
- $this->backupFactoryMock->expects($this->once())->method('create')->willReturn($this->backupDbMock);
-
- $this->backupDbMock->expects($this->once())->method('create')->willThrowException(new \Exception);
-
- $this->backupDataMock->expects($this->never())->method('getCreateSuccessMessageByType')->with($type);
- $this->loggerMock->expects($this->never())->method('info');
-
- $this->systemBackup->execute();
+ $this->helperMock->expects($this->any())->method('isEnabled')->willReturn(false);
+ $this->cron->execute();
}
}
diff --git a/app/code/Magento/Backup/Test/Unit/Model/DbTest.php b/app/code/Magento/Backup/Test/Unit/Model/DbTest.php
deleted file mode 100644
index 0cab5f0ad1e9..000000000000
--- a/app/code/Magento/Backup/Test/Unit/Model/DbTest.php
+++ /dev/null
@@ -1,243 +0,0 @@
-dbResourceMock = $this->getMockBuilder(DbResource::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->connectionResourceMock = $this->getMockBuilder(ResourceConnection::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->objectManager = new ObjectManager($this);
- $this->dbModel = $this->objectManager->getObject(
- Db::class,
- [
- 'resourceDb' => $this->dbResourceMock,
- 'resource' => $this->connectionResourceMock
- ]
- );
- }
-
- public function testGetResource()
- {
- self::assertEquals($this->dbResourceMock, $this->dbModel->getResource());
- }
-
- public function testGetTables()
- {
- $tables = [];
- $this->dbResourceMock->expects($this->once())
- ->method('getTables')
- ->willReturn($tables);
-
- self::assertEquals($tables, $this->dbModel->getTables());
- }
-
- public function testGetTableCreateScript()
- {
- $tableName = 'some_table';
- $script = 'script';
- $this->dbResourceMock->expects($this->once())
- ->method('getTableCreateScript')
- ->with($tableName, false)
- ->willReturn($script);
-
- self::assertEquals($script, $this->dbModel->getTableCreateScript($tableName, false));
- }
-
- public function testGetTableDataDump()
- {
- $tableName = 'some_table';
- $dump = 'dump';
- $this->dbResourceMock->expects($this->once())
- ->method('getTableDataDump')
- ->with($tableName)
- ->willReturn($dump);
-
- self::assertEquals($dump, $this->dbModel->getTableDataDump($tableName));
- }
-
- public function testGetHeader()
- {
- $header = 'header';
- $this->dbResourceMock->expects($this->once())
- ->method('getHeader')
- ->willReturn($header);
-
- self::assertEquals($header, $this->dbModel->getHeader());
- }
-
- public function testGetFooter()
- {
- $footer = 'footer';
- $this->dbResourceMock->expects($this->once())
- ->method('getFooter')
- ->willReturn($footer);
-
- self::assertEquals($footer, $this->dbModel->getFooter());
- }
-
- public function testRenderSql()
- {
- $header = 'header';
- $script = 'script';
- $tableName = 'some_table';
- $tables = [$tableName, $tableName];
- $dump = 'dump';
- $footer = 'footer';
-
- $this->dbResourceMock->expects($this->once())
- ->method('getTables')
- ->willReturn($tables);
- $this->dbResourceMock->expects($this->once())
- ->method('getHeader')
- ->willReturn($header);
- $this->dbResourceMock->expects($this->exactly(2))
- ->method('getTableCreateScript')
- ->with($tableName, true)
- ->willReturn($script);
- $this->dbResourceMock->expects($this->exactly(2))
- ->method('getTableDataDump')
- ->with($tableName)
- ->willReturn($dump);
- $this->dbResourceMock->expects($this->once())
- ->method('getFooter')
- ->willReturn($footer);
-
- self::assertEquals(
- $header . $script . $dump . $script . $dump . $footer,
- $this->dbModel->renderSql()
- );
- }
-
- public function testCreateBackup()
- {
- /** @var BackupInterface|\PHPUnit_Framework_MockObject_MockObject $backupMock */
- $backupMock = $this->getMockBuilder(BackupInterface::class)->getMock();
- /** @var DataObject $tableStatus */
- $tableStatus = new DataObject();
-
- $tableName = 'some_table';
- $tables = [$tableName];
- $header = 'header';
- $footer = 'footer';
- $dropSql = 'drop_sql';
- $createSql = 'create_sql';
- $beforeSql = 'before_sql';
- $afterSql = 'after_sql';
- $dataSql = 'data_sql';
- $foreignKeysSql = 'foreign_keys';
- $triggersSql = 'triggers_sql';
- $rowsCount = 2;
- $dataLength = 1;
-
- $this->dbResourceMock->expects($this->once())
- ->method('beginTransaction');
- $this->dbResourceMock->expects($this->once())
- ->method('commitTransaction');
- $this->dbResourceMock->expects($this->once())
- ->method('getTables')
- ->willReturn($tables);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableDropSql')
- ->willReturn($dropSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableCreateSql')
- ->with($tableName, false)
- ->willReturn($createSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableDataBeforeSql')
- ->with($tableName)
- ->willReturn($beforeSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableDataAfterSql')
- ->with($tableName)
- ->willReturn($afterSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableDataSql')
- ->with($tableName, $rowsCount, 0)
- ->willReturn($dataSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableStatus')
- ->with($tableName)
- ->willReturn($tableStatus);
- $this->dbResourceMock->expects($this->once())
- ->method('getTables')
- ->willReturn($createSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getHeader')
- ->willReturn($header);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableHeader')
- ->willReturn($header);
- $this->dbResourceMock->expects($this->once())
- ->method('getFooter')
- ->willReturn($footer);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableForeignKeysSql')
- ->willReturn($foreignKeysSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableTriggersSql')
- ->willReturn($triggersSql);
- $backupMock->expects($this->once())
- ->method('open');
- $backupMock->expects($this->once())
- ->method('close');
-
- $tableStatus->setRows($rowsCount);
- $tableStatus->setDataLength($dataLength);
-
- $backupMock->expects($this->any())
- ->method('write')
- ->withConsecutive(
- [$this->equalTo($header)],
- [$this->equalTo($header . $dropSql . "\n")],
- [$this->equalTo($createSql . "\n")],
- [$this->equalTo($beforeSql)],
- [$this->equalTo($dataSql)],
- [$this->equalTo($afterSql)],
- [$this->equalTo($foreignKeysSql)],
- [$this->equalTo($triggersSql)],
- [$this->equalTo($footer)]
- );
-
- $this->dbModel->createBackup($backupMock);
- }
-}
diff --git a/app/code/Magento/Backup/etc/adminhtml/system.xml b/app/code/Magento/Backup/etc/adminhtml/system.xml
index 4028452d0443..90f6fa861b40 100644
--- a/app/code/Magento/Backup/etc/adminhtml/system.xml
+++ b/app/code/Magento/Backup/etc/adminhtml/system.xml
@@ -9,13 +9,21 @@
- Scheduled Backup Settings
+ Backup Settings
+
+ Enable Backup
+ Disabled by default for security reasons
+ Magento\Config\Model\Config\Source\Yesno
+
Enable Scheduled Backup
Magento\Config\Model\Config\Source\Yesno
+
+ 1
+
- Backup Type
+ Scheduled Backup Type
1
diff --git a/app/code/Magento/Backup/etc/config.xml b/app/code/Magento/Backup/etc/config.xml
new file mode 100644
index 000000000000..fb0808983b9c
--- /dev/null
+++ b/app/code/Magento/Backup/etc/config.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ 0
+
+
+
+
diff --git a/app/code/Magento/Backup/view/adminhtml/layout/backup_index_disabled.xml b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_disabled.xml
new file mode 100644
index 000000000000..3470f528e5ce
--- /dev/null
+++ b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_disabled.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ Backup functionality is disabled
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Backup/view/adminhtml/templates/backup/disabled.phtml b/app/code/Magento/Backup/view/adminhtml/templates/backup/disabled.phtml
new file mode 100644
index 000000000000..a5308dce5cc5
--- /dev/null
+++ b/app/code/Magento/Backup/view/adminhtml/templates/backup/disabled.phtml
@@ -0,0 +1,7 @@
+
+Backup functionality is currently disabled. Please use other means for backups
diff --git a/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php b/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php
index 1acd16708ff4..418cb9390061 100644
--- a/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php
+++ b/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php
@@ -9,6 +9,7 @@
use Magento\Braintree\Model\Paypal\Helper;
use Magento\Checkout\Model\Session;
use Magento\Framework\App\Action\Context;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Exception\LocalizedException;
@@ -17,7 +18,7 @@
/**
* Class PlaceOrder
*/
-class PlaceOrder extends AbstractAction
+class PlaceOrder extends AbstractAction implements HttpPostActionInterface
{
/**
* @var Helper\OrderPlace
@@ -54,6 +55,7 @@ public function __construct(
/**
* @inheritdoc
+ *
* @throws LocalizedException
*/
public function execute()
@@ -71,7 +73,10 @@ public function execute()
return $resultRedirect->setPath('checkout/onepage/success', ['_secure' => true]);
} catch (\Exception $e) {
$this->logger->critical($e);
- $this->messageManager->addExceptionMessage($e, $e->getMessage());
+ $this->messageManager->addExceptionMessage(
+ $e,
+ 'The order #' . $quote->getReservedOrderId() . ' cannot be processed.'
+ );
}
return $resultRedirect->setPath('checkout/cart', ['_secure' => true]);
diff --git a/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php b/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php
index 6c4332ef22a4..314404c79939 100644
--- a/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php
+++ b/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php
@@ -6,14 +6,15 @@
namespace Magento\Braintree\Model\Paypal\Helper;
-use Magento\Quote\Model\Quote;
+use Magento\Braintree\Model\Paypal\OrderCancellationService;
+use Magento\Checkout\Api\AgreementsValidatorInterface;
use Magento\Checkout\Helper\Data;
+use Magento\Checkout\Model\Type\Onepage;
use Magento\Customer\Model\Group;
use Magento\Customer\Model\Session;
-use Magento\Checkout\Model\Type\Onepage;
-use Magento\Quote\Api\CartManagementInterface;
use Magento\Framework\Exception\LocalizedException;
-use Magento\Checkout\Api\AgreementsValidatorInterface;
+use Magento\Quote\Api\CartManagementInterface;
+use Magento\Quote\Model\Quote;
/**
* Class OrderPlace
@@ -42,23 +43,29 @@ class OrderPlace extends AbstractHelper
private $checkoutHelper;
/**
- * Constructor
- *
+ * @var OrderCancellationService
+ */
+ private $orderCancellationService;
+
+ /**
* @param CartManagementInterface $cartManagement
* @param AgreementsValidatorInterface $agreementsValidator
* @param Session $customerSession
* @param Data $checkoutHelper
+ * @param OrderCancellationService $orderCancellationService
*/
public function __construct(
CartManagementInterface $cartManagement,
AgreementsValidatorInterface $agreementsValidator,
Session $customerSession,
- Data $checkoutHelper
+ Data $checkoutHelper,
+ OrderCancellationService $orderCancellationService
) {
$this->cartManagement = $cartManagement;
$this->agreementsValidator = $agreementsValidator;
$this->customerSession = $customerSession;
$this->checkoutHelper = $checkoutHelper;
+ $this->orderCancellationService = $orderCancellationService;
}
/**
@@ -67,7 +74,7 @@ public function __construct(
* @param Quote $quote
* @param array $agreement
* @return void
- * @throws LocalizedException
+ * @throws \Exception
*/
public function execute(Quote $quote, array $agreement)
{
@@ -84,7 +91,12 @@ public function execute(Quote $quote, array $agreement)
$this->disabledQuoteAddressValidation($quote);
$quote->collectTotals();
- $this->cartManagement->placeOrder($quote->getId());
+ try {
+ $this->cartManagement->placeOrder($quote->getId());
+ } catch (\Exception $e) {
+ $this->orderCancellationService->execute($quote->getReservedOrderId());
+ throw $e;
+ }
}
/**
diff --git a/app/code/Magento/Braintree/Model/Paypal/OrderCancellationService.php b/app/code/Magento/Braintree/Model/Paypal/OrderCancellationService.php
new file mode 100644
index 000000000000..29757e35ea6f
--- /dev/null
+++ b/app/code/Magento/Braintree/Model/Paypal/OrderCancellationService.php
@@ -0,0 +1,77 @@
+searchCriteriaBuilder = $searchCriteriaBuilder;
+ $this->orderRepository = $orderRepository;
+ }
+
+ /**
+ * Cancels an order and authorization transaction.
+ *
+ * @param string $incrementId
+ * @return bool
+ */
+ public function execute(string $incrementId): bool
+ {
+ $order = $this->getOrder($incrementId);
+ if ($order === null) {
+ return false;
+ }
+
+ // `\Magento\Sales\Model\Service\OrderService::cancel` cannot be used for cancellation as the service uses
+ // the order repository with outdated payment method instance (ex. contains Vault instead of Braintree)
+ $order->cancel();
+ $this->orderRepository->save($order);
+ return true;
+ }
+
+ /**
+ * Gets order by increment ID.
+ *
+ * @param string $incrementId
+ * @return OrderInterface|null
+ */
+ private function getOrder(string $incrementId)
+ {
+ $searchCriteria = $this->searchCriteriaBuilder->addFilter(OrderInterface::INCREMENT_ID, $incrementId)
+ ->create();
+
+ $items = $this->orderRepository->getList($searchCriteria)
+ ->getItems();
+
+ return array_pop($items);
+ }
+}
diff --git a/app/code/Magento/Braintree/Plugin/OrderCancellation.php b/app/code/Magento/Braintree/Plugin/OrderCancellation.php
new file mode 100644
index 000000000000..90c72839d977
--- /dev/null
+++ b/app/code/Magento/Braintree/Plugin/OrderCancellation.php
@@ -0,0 +1,81 @@
+orderCancellationService = $orderCancellationService;
+ $this->quoteRepository = $quoteRepository;
+ }
+
+ /**
+ * Cancels an order if an exception occurs during the order creation.
+ *
+ * @param CartManagementInterface $subject
+ * @param \Closure $proceed
+ * @param int $cartId
+ * @param PaymentInterface $payment
+ * @return int
+ * @throws \Exception
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function aroundPlaceOrder(
+ CartManagementInterface $subject,
+ \Closure $proceed,
+ $cartId,
+ PaymentInterface $payment = null
+ ) {
+ try {
+ return $proceed($cartId, $payment);
+ } catch (\Exception $e) {
+ $quote = $this->quoteRepository->get((int) $cartId);
+ $payment = $quote->getPayment();
+ $paymentCodes = [
+ ConfigProvider::CODE,
+ ConfigProvider::CC_VAULT_CODE,
+ PayPalConfigProvider::PAYPAL_CODE,
+ PayPalConfigProvider::PAYPAL_VAULT_CODE
+ ];
+ if (in_array($payment->getMethod(), $paymentCodes)) {
+ $incrementId = $quote->getReservedOrderId();
+ $this->orderCancellationService->execute($incrementId);
+ }
+
+ throw $e;
+ }
+ }
+}
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml
new file mode 100644
index 000000000000..c2e6af9dcd73
--- /dev/null
+++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml
index 27e2039fe526..9eaae8b33e73 100644
--- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
@@ -46,4 +46,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml
index ee7158c2b63f..17d634c009b3 100644
--- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml
@@ -8,28 +8,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -56,11 +35,5 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml
index 65eddc0d9e51..4d531214db15 100644
--- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml
@@ -6,15 +6,15 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
-
-
-
-
+
+
+
+
@@ -23,6 +23,5 @@
-
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml
index 8f2588a6effa..f00e3fa286b0 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml
@@ -42,6 +42,7 @@
MerchantId
PublicKey
PrivateKey
+ Status
Credit Card (Braintree)
@@ -128,7 +129,7 @@
John
Smith
admin123
- mail@mail.com
+ mail@mail.com
diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml
index 772c1c39a04c..30345ec31bac 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml
@@ -18,6 +18,7 @@
Yerevan
9999
9999
+ Armenia
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml
index 660c7393b406..eb7a9ce2c376 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml
@@ -13,7 +13,7 @@
-
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml
index df2e98816f0d..2ddefa40b536 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml
@@ -17,9 +17,6 @@
-
-
-
@@ -28,11 +25,13 @@
-
+
+
+
+
-
-
+
@@ -48,30 +47,49 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
-
-
-
+
@@ -80,8 +98,6 @@
-
-
diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml
new file mode 100644
index 000000000000..d1d685effd13
--- /dev/null
+++ b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php
index 4bea03153b93..9c25846e56da 100644
--- a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Braintree\Test\Unit\Controller\Paypal;
@@ -16,6 +17,8 @@
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Message\ManagerInterface;
use Magento\Quote\Model\Quote;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
/**
* Class PlaceOrderTest
@@ -27,34 +30,34 @@
class PlaceOrderTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var OrderPlace|\PHPUnit_Framework_MockObject_MockObject
+ * @var OrderPlace|MockObject
*/
- private $orderPlaceMock;
+ private $orderPlace;
/**
- * @var Config|\PHPUnit_Framework_MockObject_MockObject
+ * @var Config|MockObject
*/
- private $configMock;
+ private $config;
/**
- * @var Session|\PHPUnit_Framework_MockObject_MockObject
+ * @var Session|MockObject
*/
- private $checkoutSessionMock;
+ private $checkoutSession;
/**
- * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var RequestInterface|MockObject
*/
- private $requestMock;
+ private $request;
/**
- * @var ResultFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var ResultFactory|MockObject
*/
- private $resultFactoryMock;
+ private $resultFactory;
/**
- * @var ManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var ManagerInterface|MockObject
*/
- protected $messageManagerMock;
+ private $messageManager;
/**
* @var PlaceOrder
@@ -62,139 +65,143 @@ class PlaceOrderTest extends \PHPUnit\Framework\TestCase
private $placeOrder;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
- private $loggerMock;
+ private $logger;
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
- /** @var Context|\PHPUnit_Framework_MockObject_MockObject $contextMock */
- $contextMock = $this->getMockBuilder(Context::class)
+ /** @var Context|MockObject $context */
+ $context = $this->getMockBuilder(Context::class)
->disableOriginalConstructor()
->getMock();
- $this->requestMock = $this->getMockBuilder(RequestInterface::class)
+ $this->request = $this->getMockBuilder(RequestInterface::class)
->setMethods(['getPostValue'])
->getMockForAbstractClass();
- $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class)
+ $this->resultFactory = $this->getMockBuilder(ResultFactory::class)
->disableOriginalConstructor()
->getMock();
- $this->checkoutSessionMock = $this->getMockBuilder(Session::class)
+ $this->checkoutSession = $this->getMockBuilder(Session::class)
->disableOriginalConstructor()
->getMock();
- $this->configMock = $this->getMockBuilder(Config::class)
+ $this->config = $this->getMockBuilder(Config::class)
->disableOriginalConstructor()
->getMock();
- $this->orderPlaceMock = $this->getMockBuilder(OrderPlace::class)
+ $this->orderPlace = $this->getMockBuilder(OrderPlace::class)
->disableOriginalConstructor()
->getMock();
- $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class)
+ $this->messageManager = $this->getMockBuilder(ManagerInterface::class)
->getMockForAbstractClass();
- $contextMock->expects(self::once())
- ->method('getRequest')
- ->willReturn($this->requestMock);
- $contextMock->expects(self::once())
- ->method('getResultFactory')
- ->willReturn($this->resultFactoryMock);
- $contextMock->expects(self::once())
- ->method('getMessageManager')
- ->willReturn($this->messageManagerMock);
-
- $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
+ $context->method('getRequest')
+ ->willReturn($this->request);
+ $context->method('getResultFactory')
+ ->willReturn($this->resultFactory);
+ $context->method('getMessageManager')
+ ->willReturn($this->messageManager);
+
+ $this->logger = $this->getMockBuilder(LoggerInterface::class)
->disableOriginalConstructor()
->getMock();
$this->placeOrder = new PlaceOrder(
- $contextMock,
- $this->configMock,
- $this->checkoutSessionMock,
- $this->orderPlaceMock,
- $this->loggerMock
+ $context,
+ $this->config,
+ $this->checkoutSession,
+ $this->orderPlace,
+ $this->logger
);
}
+ /**
+ * Checks if an order is placed successfully.
+ *
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NotFoundException
+ */
public function testExecute()
{
$agreement = ['test-data'];
$quoteMock = $this->getQuoteMock();
- $quoteMock->expects(self::once())
- ->method('getItemsCount')
+ $quoteMock->method('getItemsCount')
->willReturn(1);
$resultMock = $this->getResultMock();
- $resultMock->expects(self::once())
- ->method('setPath')
+ $resultMock->method('setPath')
->with('checkout/onepage/success')
->willReturnSelf();
- $this->resultFactoryMock->expects(self::once())
- ->method('create')
+ $this->resultFactory->method('create')
->with(ResultFactory::TYPE_REDIRECT)
->willReturn($resultMock);
- $this->requestMock->expects(self::once())
- ->method('getPostValue')
+ $this->request->method('getPostValue')
->with('agreement', [])
->willReturn($agreement);
- $this->checkoutSessionMock->expects(self::once())
- ->method('getQuote')
+ $this->checkoutSession->method('getQuote')
->willReturn($quoteMock);
- $this->orderPlaceMock->expects(self::once())
- ->method('execute')
+ $this->orderPlace->method('execute')
->with($quoteMock, [0]);
- $this->messageManagerMock->expects(self::never())
+ $this->messageManager->expects(self::never())
->method('addExceptionMessage');
self::assertEquals($this->placeOrder->execute(), $resultMock);
}
+ /**
+ * Checks a negative scenario during place order action.
+ *
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NotFoundException
+ */
public function testExecuteException()
{
$agreement = ['test-data'];
$quote = $this->getQuoteMock();
- $quote->expects(self::once())
- ->method('getItemsCount')
+ $quote->method('getItemsCount')
->willReturn(0);
+ $quote->method('getReservedOrderId')
+ ->willReturn('000000111');
$resultMock = $this->getResultMock();
- $resultMock->expects(self::once())
- ->method('setPath')
+ $resultMock->method('setPath')
->with('checkout/cart')
->willReturnSelf();
- $this->resultFactoryMock->expects(self::once())
- ->method('create')
+ $this->resultFactory->method('create')
->with(ResultFactory::TYPE_REDIRECT)
->willReturn($resultMock);
- $this->requestMock->expects(self::once())
- ->method('getPostValue')
+ $this->request->method('getPostValue')
->with('agreement', [])
->willReturn($agreement);
- $this->checkoutSessionMock->expects(self::once())
- ->method('getQuote')
+ $this->checkoutSession->method('getQuote')
->willReturn($quote);
- $this->orderPlaceMock->expects(self::never())
+ $this->orderPlace->expects(self::never())
->method('execute');
- $this->messageManagerMock->expects(self::once())
- ->method('addExceptionMessage')
+ $this->messageManager->method('addExceptionMessage')
->with(
self::isInstanceOf('\InvalidArgumentException'),
- 'Checkout failed to initialize. Verify and try again.'
+ 'The order #000000111 cannot be processed.'
);
self::assertEquals($this->placeOrder->execute(), $resultMock);
}
/**
- * @return ResultInterface|\PHPUnit_Framework_MockObject_MockObject
+ * Gets mock object for a result.
+ *
+ * @return ResultInterface|MockObject
*/
private function getResultMock()
{
@@ -204,7 +211,9 @@ private function getResultMock()
}
/**
- * @return Quote|\PHPUnit_Framework_MockObject_MockObject
+ * Gets mock object for a quote.
+ *
+ * @return Quote|MockObject
*/
private function getQuoteMock()
{
diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/OrderPlaceTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/OrderPlaceTest.php
index 1aecba91b9af..c8524017274a 100644
--- a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/OrderPlaceTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/OrderPlaceTest.php
@@ -3,9 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Braintree\Test\Unit\Model\Paypal\Helper;
use Magento\Braintree\Model\Paypal\Helper\OrderPlace;
+use Magento\Braintree\Model\Paypal\OrderCancellationService;
use Magento\Checkout\Api\AgreementsValidatorInterface;
use Magento\Checkout\Helper\Data;
use Magento\Checkout\Model\Type\Onepage;
@@ -14,6 +17,7 @@
use Magento\Quote\Api\CartManagementInterface;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Address;
+use PHPUnit\Framework\MockObject\MockObject;
/**
* Class OrderPlaceTest
@@ -27,62 +31,80 @@ class OrderPlaceTest extends \PHPUnit\Framework\TestCase
const TEST_EMAIL = 'test@test.loc';
/**
- * @var CartManagementInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var CartManagementInterface|MockObject
*/
- private $cartManagementMock;
+ private $cartManagement;
/**
- * @var AgreementsValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var AgreementsValidatorInterface|MockObject
*/
- private $agreementsValidatorMock;
+ private $agreementsValidator;
/**
- * @var Session|\PHPUnit_Framework_MockObject_MockObject
+ * @var Session|MockObject
*/
- private $customerSessionMock;
+ private $customerSession;
/**
- * @var Data|\PHPUnit_Framework_MockObject_MockObject
+ * @var Data|MockObject
*/
- private $checkoutHelperMock;
+ private $checkoutHelper;
/**
- * @var Address|\PHPUnit_Framework_MockObject_MockObject
+ * @var Address|MockObject
*/
- private $billingAddressMock;
+ private $billingAddress;
/**
* @var OrderPlace
*/
private $orderPlace;
+ /**
+ * @var OrderCancellationService|MockObject
+ */
+ private $orderCancellation;
+
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
- $this->cartManagementMock = $this->getMockBuilder(CartManagementInterface::class)
+ $this->cartManagement = $this->getMockBuilder(CartManagementInterface::class)
->getMockForAbstractClass();
- $this->agreementsValidatorMock = $this->getMockBuilder(AgreementsValidatorInterface::class)
+ $this->agreementsValidator = $this->getMockBuilder(AgreementsValidatorInterface::class)
->getMockForAbstractClass();
- $this->customerSessionMock = $this->getMockBuilder(Session::class)
+ $this->customerSession = $this->getMockBuilder(Session::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->checkoutHelper = $this->getMockBuilder(Data::class)
->disableOriginalConstructor()
->getMock();
- $this->checkoutHelperMock = $this->getMockBuilder(Data::class)
+
+ $this->orderCancellation = $this->getMockBuilder(OrderCancellationService::class)
->disableOriginalConstructor()
->getMock();
$this->orderPlace = new OrderPlace(
- $this->cartManagementMock,
- $this->agreementsValidatorMock,
- $this->customerSessionMock,
- $this->checkoutHelperMock
+ $this->cartManagement,
+ $this->agreementsValidator,
+ $this->customerSession,
+ $this->checkoutHelper,
+ $this->orderCancellation
);
}
+ /**
+ * Checks a scenario for a guest customer.
+ *
+ * @throws \Exception
+ */
public function testExecuteGuest()
{
$agreement = ['test', 'test'];
$quoteMock = $this->getQuoteMock();
- $this->agreementsValidatorMock->expects(self::once())
+ $this->agreementsValidator->expects(self::once())
->method('isValid')
->willReturn(true);
@@ -97,7 +119,7 @@ public function testExecuteGuest()
->method('getId')
->willReturn(10);
- $this->cartManagementMock->expects(self::once())
+ $this->cartManagement->expects(self::once())
->method('placeOrder')
->with(10);
@@ -105,9 +127,11 @@ public function testExecuteGuest()
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
+ * Disables address validation.
+ *
+ * @param MockObject $quoteMock
*/
- private function disabledQuoteAddressValidationStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock)
+ private function disabledQuoteAddressValidationStep(MockObject $quoteMock)
{
$billingAddressMock = $this->getBillingAddressMock($quoteMock);
$shippingAddressMock = $this->getMockBuilder(Address::class)
@@ -115,26 +139,21 @@ private function disabledQuoteAddressValidationStep(\PHPUnit_Framework_MockObjec
->disableOriginalConstructor()
->getMock();
- $quoteMock->expects(self::once())
- ->method('getShippingAddress')
+ $quoteMock->method('getShippingAddress')
->willReturn($shippingAddressMock);
- $billingAddressMock->expects(self::once())
- ->method('setShouldIgnoreValidation')
+ $billingAddressMock->method('setShouldIgnoreValidation')
->with(true)
->willReturnSelf();
- $quoteMock->expects(self::once())
- ->method('getIsVirtual')
+ $quoteMock->method('getIsVirtual')
->willReturn(false);
- $shippingAddressMock->expects(self::once())
- ->method('setShouldIgnoreValidation')
+ $shippingAddressMock->method('setShouldIgnoreValidation')
->with(true)
->willReturnSelf();
- $billingAddressMock->expects(self::any())
- ->method('getEmail')
+ $billingAddressMock->method('getEmail')
->willReturn(self::TEST_EMAIL);
$billingAddressMock->expects(self::never())
@@ -142,25 +161,24 @@ private function disabledQuoteAddressValidationStep(\PHPUnit_Framework_MockObjec
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
+ * Prepares checkout step.
+ *
+ * @param MockObject $quoteMock
*/
- private function getCheckoutMethodStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock)
+ private function getCheckoutMethodStep(MockObject $quoteMock)
{
- $this->customerSessionMock->expects(self::once())
- ->method('isLoggedIn')
+ $this->customerSession->method('isLoggedIn')
->willReturn(false);
$quoteMock->expects(self::at(1))
->method('getCheckoutMethod')
->willReturn(null);
- $this->checkoutHelperMock->expects(self::once())
- ->method('isAllowedGuestCheckout')
+ $this->checkoutHelper->method('isAllowedGuestCheckout')
->with($quoteMock)
->willReturn(true);
- $quoteMock->expects(self::once())
- ->method('setCheckoutMethod')
+ $quoteMock->method('setCheckoutMethod')
->with(Onepage::METHOD_GUEST);
$quoteMock->expects(self::at(2))
@@ -169,9 +187,11 @@ private function getCheckoutMethodStep(\PHPUnit_Framework_MockObject_MockObject
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
+ * Prepares quote.
+ *
+ * @param MockObject $quoteMock
*/
- private function prepareGuestQuoteStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock)
+ private function prepareGuestQuoteStep(MockObject $quoteMock)
{
$billingAddressMock = $this->getBillingAddressMock($quoteMock);
@@ -184,44 +204,44 @@ private function prepareGuestQuoteStep(\PHPUnit_Framework_MockObject_MockObject
->method('getEmail')
->willReturn(self::TEST_EMAIL);
- $quoteMock->expects(self::once())
- ->method('setCustomerEmail')
+ $quoteMock->method('setCustomerEmail')
->with(self::TEST_EMAIL)
->willReturnSelf();
- $quoteMock->expects(self::once())
- ->method('setCustomerIsGuest')
+ $quoteMock->method('setCustomerIsGuest')
->with(true)
->willReturnSelf();
- $quoteMock->expects(self::once())
- ->method('setCustomerGroupId')
+ $quoteMock->method('setCustomerGroupId')
->with(Group::NOT_LOGGED_IN_ID)
->willReturnSelf();
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
- * @return Address|\PHPUnit_Framework_MockObject_MockObject
+ * Gets a mock object for a billing address entity.
+ *
+ * @param MockObject $quoteMock
+ * @return Address|MockObject
*/
- private function getBillingAddressMock(\PHPUnit_Framework_MockObject_MockObject $quoteMock)
+ private function getBillingAddressMock(MockObject $quoteMock)
{
- if (!isset($this->billingAddressMock)) {
- $this->billingAddressMock = $this->getMockBuilder(Address::class)
+ if (!isset($this->billingAddress)) {
+ $this->billingAddress = $this->getMockBuilder(Address::class)
->setMethods(['setShouldIgnoreValidation', 'getEmail', 'setSameAsBilling'])
->disableOriginalConstructor()
->getMock();
}
- $quoteMock->expects(self::any())
- ->method('getBillingAddress')
- ->willReturn($this->billingAddressMock);
+ $quoteMock->method('getBillingAddress')
+ ->willReturn($this->billingAddress);
- return $this->billingAddressMock;
+ return $this->billingAddress;
}
/**
- * @return Quote|\PHPUnit_Framework_MockObject_MockObject
+ * Gets a mock object for a quote.
+ *
+ * @return Quote|MockObject
*/
private function getQuoteMock()
{
diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml
index 67c90e6991e2..b81513caf17a 100644
--- a/app/code/Magento/Braintree/etc/di.xml
+++ b/app/code/Magento/Braintree/etc/di.xml
@@ -624,4 +624,8 @@
+
+
+
+
diff --git a/app/code/Magento/Braintree/i18n/en_US.csv b/app/code/Magento/Braintree/i18n/en_US.csv
index 7bd305f546dc..e9145b35b56e 100644
--- a/app/code/Magento/Braintree/i18n/en_US.csv
+++ b/app/code/Magento/Braintree/i18n/en_US.csv
@@ -193,3 +193,4 @@ Currency,Currency
"Too many concurrent attempts to void this transaction. Try again later.","Too many concurrent attempts to void this transaction. Try again later."
"Braintree Settlement","Braintree Settlement"
"The Payment Token is not available to perform the request.","The Payment Token is not available to perform the request."
+"The order #%1 cannot be processed.","The order #%1 cannot be processed."
diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js
index abf434bc6da2..eaebd8492b0a 100644
--- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js
+++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js
@@ -7,7 +7,6 @@
define([
'jquery',
'underscore',
- 'mage/utils/wrapper',
'Magento_Checkout/js/view/payment/default',
'Magento_Braintree/js/view/payment/adapter',
'Magento_Checkout/js/model/quote',
@@ -19,7 +18,6 @@ define([
], function (
$,
_,
- wrapper,
Component,
Braintree,
quote,
@@ -105,6 +103,12 @@ define([
}
});
+ quote.shippingAddress.subscribe(function () {
+ if (self.isActive()) {
+ self.reInitPayPal();
+ }
+ });
+
// for each component initialization need update property
this.isReviewRequired(false);
this.initClientConfig();
@@ -222,9 +226,8 @@ define([
/**
* Re-init PayPal Auth Flow
- * @param {Function} callback - Optional callback
*/
- reInitPayPal: function (callback) {
+ reInitPayPal: function () {
if (Braintree.checkout) {
Braintree.checkout.teardown(function () {
Braintree.checkout = null;
@@ -235,17 +238,6 @@ define([
this.clientConfig.paypal.amount = this.grandTotalAmount;
this.clientConfig.paypal.shippingAddressOverride = this.getShippingAddress();
- if (callback) {
- this.clientConfig.onReady = wrapper.wrap(
- this.clientConfig.onReady,
- function (original, checkout) {
- this.clientConfig.onReady = original;
- original(checkout);
- callback();
- }.bind(this)
- );
- }
-
Braintree.setConfig(this.clientConfig);
Braintree.setup();
},
@@ -428,19 +420,17 @@ define([
* Triggers when customer click "Continue to PayPal" button
*/
payWithPayPal: function () {
- this.reInitPayPal(function () {
- if (!additionalValidators.validate()) {
- return;
- }
+ if (!additionalValidators.validate()) {
+ return;
+ }
- try {
- Braintree.checkout.paypal.initAuthFlow();
- } catch (e) {
- this.messageContainer.addErrorMessage({
- message: $t('Payment ' + this.getTitle() + ' can\'t be initialized.')
- });
- }
- }.bind(this));
+ try {
+ Braintree.checkout.paypal.initAuthFlow();
+ } catch (e) {
+ this.messageContainer.addErrorMessage({
+ message: $t('Payment ' + this.getTitle() + ' can\'t be initialized.')
+ });
+ }
},
/**
diff --git a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Checkbox.php b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Checkbox.php
index b220e2c98d77..46db8a990734 100644
--- a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Checkbox.php
+++ b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Checkbox.php
@@ -20,9 +20,7 @@ class Checkbox extends \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Op
protected $_template = 'Magento_Bundle::product/composite/fieldset/options/type/checkbox.phtml';
/**
- * @param string $elementId
- * @param string $containerId
- * @return string
+ * @inheritdoc
*/
public function setValidationContainer($elementId, $containerId)
{
@@ -34,4 +32,15 @@ public function setValidationContainer($elementId, $containerId)
'\';
';
}
+
+ /**
+ * @inheritdoc
+ */
+ public function getSelectionPrice($selection)
+ {
+ $price = parent::getSelectionPrice($selection);
+ $qty = $selection->getSelectionQty();
+
+ return $price * $qty;
+ }
}
diff --git a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Multi.php b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Multi.php
index a4b8c6bde73a..629f08dc7510 100644
--- a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Multi.php
+++ b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Options/Type/Multi.php
@@ -20,9 +20,7 @@ class Multi extends \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Optio
protected $_template = 'Magento_Bundle::product/composite/fieldset/options/type/multi.phtml';
/**
- * @param string $elementId
- * @param string $containerId
- * @return string
+ * @inheritdoc
*/
public function setValidationContainer($elementId, $containerId)
{
@@ -34,4 +32,15 @@ public function setValidationContainer($elementId, $containerId)
'\';
';
}
+
+ /**
+ * @inheritdoc
+ */
+ public function getSelectionPrice($selection)
+ {
+ $price = parent::getSelectionPrice($selection);
+ $qty = $selection->getSelectionQty();
+
+ return $price * $qty;
+ }
}
diff --git a/app/code/Magento/Bundle/Model/Product/Type.php b/app/code/Magento/Bundle/Model/Product/Type.php
index b61df8d7cb12..641f4490874d 100644
--- a/app/code/Magento/Bundle/Model/Product/Type.php
+++ b/app/code/Magento/Bundle/Model/Product/Type.php
@@ -737,7 +737,7 @@ protected function _prepareProduct(\Magento\Framework\DataObject $buyRequest, $p
* for selection (not for all bundle)
*/
$price = $product->getPriceModel()
- ->getSelectionFinalTotalPrice($product, $selection, 0, $qty);
+ ->getSelectionFinalTotalPrice($product, $selection, 0, 1);
$attributes = [
'price' => $price,
'qty' => $qty,
@@ -823,11 +823,11 @@ private function recursiveIntval(array $array)
private function multiToFlatArray(array $array)
{
$flatArray = [];
- foreach ($array as $key => $value) {
+ foreach ($array as $value) {
if (is_array($value)) {
$flatArray = array_merge($flatArray, $this->multiToFlatArray($value));
} else {
- $flatArray[$key] = $value;
+ $flatArray[] = $value;
}
}
diff --git a/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php b/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php
index adb0777151b9..04a6ee0bd459 100644
--- a/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php
+++ b/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php
@@ -198,6 +198,8 @@ protected function getSelectionAmounts(Product $bundleProduct, $searchMin, $useR
}
/**
+ * Get selection price list provider.
+ *
* @return SelectionPriceListProviderInterface
* @deprecated 100.2.0
*/
@@ -281,7 +283,7 @@ public function calculateBundleAmount($basePriceValue, $bundleProduct, $selectio
* @param float $basePriceValue
* @param Product $bundleProduct
* @param \Magento\Bundle\Pricing\Price\BundleSelectionPrice[] $selectionPriceList
- * @param null|bool|string|arrayy $exclude
+ * @param null|bool|string|array $exclude
* @return \Magento\Framework\Pricing\Amount\AmountInterface
*/
protected function calculateFixedBundleAmount($basePriceValue, $bundleProduct, $selectionPriceList, $exclude)
diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
index f28ffbdc40ac..e36730a87b41 100644
--- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
@@ -24,4 +24,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml
index 3a70b189b4dc..c6a07f7ed95c 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml
@@ -15,9 +15,6 @@
-
-
-
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml
new file mode 100644
index 000000000000..33181d6e920e
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/etc/di.xml b/app/code/Magento/Bundle/etc/di.xml
index 6f0cc04790cc..72155d922a25 100644
--- a/app/code/Magento/Bundle/etc/di.xml
+++ b/app/code/Magento/Bundle/etc/di.xml
@@ -143,6 +143,13 @@
+
+
+
+ - Magento\Bundle\Model\ProductOptionProcessor
+
+
+
diff --git a/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php
index b0794f456464..a8650a4e6e9e 100644
--- a/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php
+++ b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php
@@ -242,7 +242,7 @@ public function testSaveData($skus, $bunch, $allowImport)
'price_type' => 'fixed',
'shipment_type' => '1',
'default_qty' => '1',
- 'is_defaul' => '1',
+ 'is_default' => '1',
'position' => '1',
'option_id' => '1']
]
@@ -264,7 +264,7 @@ public function testSaveData($skus, $bunch, $allowImport)
'price_type' => 'percent',
'shipment_type' => 0,
'default_qty' => '2',
- 'is_defaul' => '1',
+ 'is_default' => '1',
'position' => '6',
'option_id' => '6']
]
@@ -324,7 +324,7 @@ public function saveDataProvider()
. 'price_type=fixed,'
. 'shipment_type=separately,'
. 'default_qty=1,'
- . 'is_defaul=1,'
+ . 'is_default=1,'
. 'position=1,'
. 'option_id=1 | name=Bundle2,'
. 'type=dropdown,'
@@ -333,7 +333,7 @@ public function saveDataProvider()
. 'price=10,'
. 'price_type=fixed,'
. 'default_qty=1,'
- . 'is_defaul=1,'
+ . 'is_default=1,'
. 'position=2,'
. 'option_id=2'
],
diff --git a/app/code/Magento/Captcha/CustomerData/Captcha.php b/app/code/Magento/Captcha/CustomerData/Captcha.php
new file mode 100644
index 000000000000..a744daacdc67
--- /dev/null
+++ b/app/code/Magento/Captcha/CustomerData/Captcha.php
@@ -0,0 +1,61 @@
+helper = $helper;
+ $this->formIds = $formIds;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getSectionData() :array
+ {
+ $data = [];
+
+ foreach ($this->formIds as $formId) {
+ $captchaModel = $this->helper->getCaptcha($formId);
+ $data[$formId] = [
+ 'isRequired' => $captchaModel->isRequired(),
+ 'timestamp' => time()
+ ];
+ }
+
+ return $data;
+ }
+}
diff --git a/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php b/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php
index ef5f5a8edce7..34ee62044ff5 100644
--- a/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php
+++ b/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Captcha\Model\Checkout;
+/**
+ * Configuration provider for Captcha rendering.
+ */
class ConfigProvider implements \Magento\Checkout\Model\ConfigProviderInterface
{
/**
@@ -38,7 +41,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getConfig()
{
@@ -49,7 +52,8 @@ public function getConfig()
'imageHeight' => $this->getImageHeight($formId),
'imageSrc' => $this->getImageSrc($formId),
'refreshUrl' => $this->getRefreshUrl(),
- 'isRequired' => $this->isRequired($formId)
+ 'isRequired' => $this->isRequired($formId),
+ 'timestamp' => time()
];
}
return $config;
diff --git a/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php b/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php
index 91f3a785df36..84ac71046c34 100644
--- a/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php
+++ b/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php
@@ -10,6 +10,9 @@
use Magento\Framework\Session\SessionManagerInterface;
use Magento\Framework\Controller\Result\JsonFactory;
+/**
+ * Around plugin for login action.
+ */
class AjaxLogin
{
/**
@@ -61,6 +64,8 @@ public function __construct(
}
/**
+ * Check captcha data on login action.
+ *
* @param \Magento\Customer\Controller\Ajax\Login $subject
* @param \Closure $proceed
* @return $this
@@ -94,18 +99,20 @@ public function aroundExecute(
if ($formId === $loginFormId) {
$captchaModel = $this->helper->getCaptcha($formId);
if ($captchaModel->isRequired($username)) {
- $captchaModel->logAttempt($username);
if (!$captchaModel->isCorrect($captchaString)) {
$this->sessionManager->setUsername($username);
+ $captchaModel->logAttempt($username);
return $this->returnJsonError(__('Incorrect CAPTCHA'));
}
}
+ $captchaModel->logAttempt($username);
}
}
return $proceed();
}
/**
+ * Format JSON response.
*
* @param \Magento\Framework\Phrase $phrase
* @return \Magento\Framework\Controller\Result\Json
diff --git a/app/code/Magento/Captcha/Model/DefaultModel.php b/app/code/Magento/Captcha/Model/DefaultModel.php
index cf6690df5c85..483f9c3fb4d2 100644
--- a/app/code/Magento/Captcha/Model/DefaultModel.php
+++ b/app/code/Magento/Captcha/Model/DefaultModel.php
@@ -78,6 +78,11 @@ class DefaultModel extends \Zend\Captcha\Image implements \Magento\Captcha\Model
*/
protected $session;
+ /**
+ * @var string
+ */
+ private $words;
+
/**
* @param \Magento\Framework\Session\SessionManagerInterface $session
* @param \Magento\Captcha\Helper\Data $captchaData
@@ -311,18 +316,18 @@ public function getImgUrl()
*/
public function isCorrect($word)
{
- $storedWord = $this->getWord();
+ $storedWords = $this->getWords();
$this->clearWord();
- if (!$word || !$storedWord) {
+ if (!$word || !$storedWords) {
return false;
}
if (!$this->isCaseSensitive()) {
- $storedWord = strtolower($storedWord);
+ $storedWords = strtolower($storedWords);
$word = strtolower($word);
}
- return $word === $storedWord;
+ return in_array($word, explode(',', $storedWords));
}
/**
@@ -481,7 +486,7 @@ private function getTargetForms()
/**
* Get captcha word
*
- * @return string
+ * @return string|null
*/
public function getWord()
{
@@ -489,6 +494,17 @@ public function getWord()
return time() < $sessionData['expires'] ? $sessionData['data'] : null;
}
+ /**
+ * Get captcha words
+ *
+ * @return string|null
+ */
+ private function getWords()
+ {
+ $sessionData = $this->session->getData($this->getFormIdKey(self::SESSION_WORD));
+ return time() < $sessionData['expires'] ? $sessionData['words'] : null;
+ }
+
/**
* Set captcha word
*
@@ -498,9 +514,10 @@ public function getWord()
*/
protected function setWord($word)
{
+ $this->words = $this->words ? $this->words . ',' . $word : $word;
$this->session->setData(
$this->getFormIdKey(self::SESSION_WORD),
- ['data' => $word, 'expires' => time() + $this->getTimeout()]
+ ['data' => $word, 'words' => $this->words, 'expires' => time() + $this->getTimeout()]
);
$this->word = $word;
return $this;
diff --git a/app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
similarity index 52%
rename from app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml
rename to app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
index 0ca252aa7354..7a0557c4a274 100644
--- a/app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
@@ -8,7 +8,9 @@
-
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml
index 8f764899706a..a088266f760a 100644
--- a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml
@@ -64,6 +64,52 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php b/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php
index 655fcd6118e2..8764dbd4cec1 100644
--- a/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php
+++ b/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php
@@ -77,6 +77,7 @@ public function testGetConfig($isRequired, $captchaGenerations, $expectedConfig)
->will($this->returnValue('https://magento.com/captcha'));
$config = $this->model->getConfig();
+ unset($config['captcha'][$this->formId]['timestamp']);
$this->assertEquals($config, $expectedConfig);
}
diff --git a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php
index 1edbcc029e4c..eef75d2c01ec 100644
--- a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php
+++ b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php
@@ -183,7 +183,13 @@ public function testIsCorrect()
{
self::$_defaultConfig['case_sensitive'] = '1';
$this->assertFalse($this->_object->isCorrect('abcdef5'));
- $sessionData = ['user_create_word' => ['data' => 'AbCdEf5', 'expires' => time() + self::EXPIRE_FRAME]];
+ $sessionData = [
+ 'user_create_word' => [
+ 'data' => 'AbCdEf5',
+ 'words' => 'AbCdEf5',
+ 'expires' => time() + self::EXPIRE_FRAME
+ ]
+ ];
$this->_object->getSession()->setData($sessionData);
self::$_defaultConfig['case_sensitive'] = '0';
$this->assertTrue($this->_object->isCorrect('abcdef5'));
@@ -224,7 +230,7 @@ public function testGetWord()
{
$this->assertEquals($this->_object->getWord(), 'AbCdEf5');
$this->_object->getSession()->setData(
- ['user_create_word' => ['data' => 'AbCdEf5', 'expires' => time() - 360]]
+ ['user_create_word' => ['data' => 'AbCdEf5', 'words' => 'AbCdEf5','expires' => time() - 360]]
);
$this->assertNull($this->_object->getWord());
}
@@ -247,7 +253,13 @@ protected function _getSessionStub()
->getMock();
$session->expects($this->any())->method('isLoggedIn')->will($this->returnValue(false));
- $session->setData(['user_create_word' => ['data' => 'AbCdEf5', 'expires' => time() + self::EXPIRE_FRAME]]);
+ $session->setData([
+ 'user_create_word' => [
+ 'data' => 'AbCdEf5',
+ 'words' => 'AbCdEf5',
+ 'expires' => time() + self::EXPIRE_FRAME
+ ]
+ ]);
return $session;
}
diff --git a/app/code/Magento/Captcha/etc/db_schema.xml b/app/code/Magento/Captcha/etc/db_schema.xml
index b8987363569c..158e2f43b9f5 100644
--- a/app/code/Magento/Captcha/etc/db_schema.xml
+++ b/app/code/Magento/Captcha/etc/db_schema.xml
@@ -9,7 +9,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
-
+
diff --git a/app/code/Magento/Captcha/etc/di.xml b/app/code/Magento/Captcha/etc/di.xml
index 3a929f5e6cc0..83c4e8aa1e2c 100644
--- a/app/code/Magento/Captcha/etc/di.xml
+++ b/app/code/Magento/Captcha/etc/di.xml
@@ -27,7 +27,7 @@
-
+
diff --git a/app/code/Magento/Captcha/etc/frontend/di.xml b/app/code/Magento/Captcha/etc/frontend/di.xml
index 0c4ab0cda073..490f1eab8519 100644
--- a/app/code/Magento/Captcha/etc/frontend/di.xml
+++ b/app/code/Magento/Captcha/etc/frontend/di.xml
@@ -20,4 +20,18 @@
+
+
+
+ - user_login
+
+
+
+
+
+
+ - Magento\Captcha\CustomerData\Captcha
+
+
+
diff --git a/app/code/Magento/Captcha/etc/frontend/sections.xml b/app/code/Magento/Captcha/etc/frontend/sections.xml
new file mode 100644
index 000000000000..7f2070e10c8a
--- /dev/null
+++ b/app/code/Magento/Captcha/etc/frontend/sections.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/view/frontend/requirejs-config.js b/app/code/Magento/Captcha/view/frontend/requirejs-config.js
index 0f3394e41e7c..42c80632d3e9 100644
--- a/app/code/Magento/Captcha/view/frontend/requirejs-config.js
+++ b/app/code/Magento/Captcha/view/frontend/requirejs-config.js
@@ -6,7 +6,8 @@
var config = {
map: {
'*': {
- captcha: 'Magento_Captcha/js/captcha'
+ captcha: 'Magento_Captcha/js/captcha',
+ 'Magento_Captcha/captcha': 'Magento_Captcha/js/captcha'
}
}
};
diff --git a/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js b/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js
index 3a235df73a91..e79cfb35ee08 100644
--- a/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js
+++ b/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js
@@ -17,11 +17,12 @@ define([
imageSource: ko.observable(captchaData.imageSrc),
visibility: ko.observable(false),
captchaValue: ko.observable(null),
- isRequired: captchaData.isRequired,
+ isRequired: ko.observable(captchaData.isRequired),
isCaseSensitive: captchaData.isCaseSensitive,
imageHeight: captchaData.imageHeight,
refreshUrl: captchaData.refreshUrl,
isLoading: ko.observable(false),
+ timestamp: null,
/**
* @return {String}
@@ -41,7 +42,7 @@ define([
* @return {Boolean}
*/
getIsVisible: function () {
- return this.visibility;
+ return this.visibility();
},
/**
@@ -55,14 +56,14 @@ define([
* @return {Boolean}
*/
getIsRequired: function () {
- return this.isRequired;
+ return this.isRequired();
},
/**
* @param {Boolean} flag
*/
setIsRequired: function (flag) {
- this.isRequired = flag;
+ this.isRequired(flag);
},
/**
diff --git a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js
index f80b2ab163ff..d79c42a71156 100644
--- a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js
+++ b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js
@@ -7,8 +7,10 @@ define([
'jquery',
'uiComponent',
'Magento_Captcha/js/model/captcha',
- 'Magento_Captcha/js/model/captchaList'
-], function ($, Component, Captcha, captchaList) {
+ 'Magento_Captcha/js/model/captchaList',
+ 'Magento_Customer/js/customer-data',
+ 'underscore'
+], function ($, Component, Captcha, captchaList, customerData, _) {
'use strict';
var captchaConfig;
@@ -34,12 +36,49 @@ define([
if (window[this.configSource] && window[this.configSource].captcha) {
captchaConfig = window[this.configSource].captcha;
$.each(captchaConfig, function (formId, captchaData) {
+ var captcha;
+
captchaData.formId = formId;
- captchaList.add(Captcha(captchaData));
- });
+ captcha = Captcha(captchaData);
+ this.checkCustomerData(formId, customerData.get('captcha')(), captcha);
+ this.subscribeCustomerData(formId, captcha);
+ captchaList.add(captcha);
+ }.bind(this));
}
},
+ /**
+ * Check customer data for captcha configuration.
+ *
+ * @param {String} formId
+ * @param {Object} captchaData
+ * @param {Object} captcha
+ */
+ checkCustomerData: function (formId, captchaData, captcha) {
+ if (!_.isEmpty(captchaData) &&
+ !_.isEmpty(captchaData)[formId] &&
+ captchaData[formId].timestamp > captcha.timestamp
+ ) {
+ if (!captcha.isRequired() && captchaData[formId].isRequired) {
+ captcha.refresh();
+ }
+ captcha.isRequired(captchaData[formId].isRequired);
+ captcha.timestamp = captchaData[formId].timestamp;
+ }
+ },
+
+ /**
+ * Subscribe for customer data updates.
+ *
+ * @param {String} formId
+ * @param {Object} captcha
+ */
+ subscribeCustomerData: function (formId, captcha) {
+ customerData.get('captcha').subscribe(function (captchaData) {
+ this.checkCustomerData(formId, captchaData, captcha);
+ }.bind(this));
+ },
+
/**
* @return {Boolean}
*/
@@ -89,6 +128,15 @@ define([
return this.currentCaptcha !== null ? this.currentCaptcha.getIsRequired() : false;
},
+ /**
+ * Set isRequired on current captcha model.
+ *
+ * @param {Boolean} flag
+ */
+ setIsRequired: function (flag) {
+ this.currentCaptcha.setIsRequired(flag);
+ },
+
/**
* @return {Boolean}
*/
diff --git a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js
index 7709febea60a..49528f6ce850 100644
--- a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js
+++ b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js
@@ -6,9 +6,10 @@
define([
'Magento_Captcha/js/view/checkout/defaultCaptcha',
'Magento_Captcha/js/model/captchaList',
- 'Magento_Customer/js/action/login'
+ 'Magento_Customer/js/action/login',
+ 'underscore'
],
-function (defaultCaptcha, captchaList, loginAction) {
+function (defaultCaptcha, captchaList, loginAction, _) {
'use strict';
return defaultCaptcha.extend({
@@ -26,9 +27,10 @@ function (defaultCaptcha, captchaList, loginAction) {
loginAction.registerLoginCallback(function (loginData) {
if (loginData['captcha_form_id'] &&
- loginData['captcha_form_id'] == self.formId //eslint-disable-line eqeqeq
+ loginData['captcha_form_id'] === self.formId &&
+ self.isRequired()
) {
- self.refresh();
+ _.defer(self.refresh.bind(self));
}
});
}
diff --git a/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html b/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html
index 575b3ca6f732..3f48ec330c0a 100644
--- a/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html
+++ b/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html
@@ -4,12 +4,12 @@
* See COPYING.txt for license details.
*/
-->
+
-
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php
index 1b188de40710..3b9036c1fbbc 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php
@@ -6,13 +6,13 @@
namespace Magento\Catalog\Block\Adminhtml\Product\Attribute\Set;
/**
- * Adminhtml Catalog Attribute Set Main Block
- *
* @author Magento Core Team
*/
use Magento\Catalog\Model\Entity\Product\Attribute\Group\AttributeMapperInterface;
/**
+ * Adminhtml Catalog Attribute Set Main Block.
+ *
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
@@ -140,7 +140,7 @@ protected function _prepareLayout()
) . '\', \'' . $this->getUrl(
'catalog/*/delete',
['id' => $setId]
- ) . '\')',
+ ) . '\',{data: {}})',
'class' => 'delete'
]
);
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php
index a08142c10be5..2df0ff0b6cd7 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php
@@ -64,17 +64,6 @@ public function __construct(
parent::__construct($context, $registry, $formFactory, $data);
}
- /**
- * Construct block
- *
- * @return void
- */
- protected function _construct()
- {
- parent::_construct();
- $this->setShowGlobalIcon(true);
- }
-
/**
* Prepares form
*
diff --git a/app/code/Magento/Catalog/Block/Product/AbstractProduct.php b/app/code/Magento/Catalog/Block/Product/AbstractProduct.php
index 4102c82a0a31..c8da0f70f73b 100644
--- a/app/code/Magento/Catalog/Block/Product/AbstractProduct.php
+++ b/app/code/Magento/Catalog/Block/Product/AbstractProduct.php
@@ -125,6 +125,7 @@ public function __construct(\Magento\Catalog\Block\Product\Context $context, arr
/**
* Retrieve url for add product to cart
+ *
* Will return product view page URL if product has required options
*
* @param \Magento\Catalog\Model\Product $product
@@ -473,7 +474,9 @@ public function getProductDetailsHtml(\Magento\Catalog\Model\Product $product)
}
/**
- * @param null $type
+ * Get the renderer that will be used to render the details block
+ *
+ * @param string|null $type
* @return bool|\Magento\Framework\View\Element\AbstractBlock
*/
public function getDetailsRenderer($type = null)
@@ -489,6 +492,8 @@ public function getDetailsRenderer($type = null)
}
/**
+ * Return the list of details
+ *
* @return \Magento\Framework\View\Element\RendererList
*/
protected function getDetailsRendererList()
diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php
index 0c547f81c85d..596cd7cc5bdc 100644
--- a/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php
+++ b/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php
@@ -9,6 +9,9 @@
*/
namespace Magento\Catalog\Block\Product\ProductList;
+/**
+ * Crosssell block for product
+ */
class Crosssell extends \Magento\Catalog\Block\Product\AbstractProduct
{
/**
@@ -25,7 +28,7 @@ class Crosssell extends \Magento\Catalog\Block\Product\AbstractProduct
*/
protected function _prepareData()
{
- $product = $this->_coreRegistry->registry('product');
+ $product = $this->getProduct();
/* @var $product \Magento\Catalog\Model\Product */
$this->_itemCollection = $product->getCrossSellProductCollection()->addAttributeToSelect(
@@ -43,6 +46,7 @@ protected function _prepareData()
/**
* Before rendering html process
+ *
* Prepare items collection
*
* @return \Magento\Catalog\Block\Product\ProductList\Crosssell
diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php
index 219922f9e46d..6de70bb97136 100644
--- a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php
+++ b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php
@@ -77,11 +77,13 @@ public function __construct(
}
/**
+ * Prepare data
+ *
* @return $this
*/
protected function _prepareData()
{
- $product = $this->_coreRegistry->registry('product');
+ $product = $this->getProduct();
/* @var $product \Magento\Catalog\Model\Product */
$this->_itemCollection = $product->getRelatedProductCollection()->addAttributeToSelect(
@@ -103,6 +105,8 @@ protected function _prepareData()
}
/**
+ * Before to html handler
+ *
* @return $this
*/
protected function _beforeToHtml()
@@ -112,6 +116,8 @@ protected function _beforeToHtml()
}
/**
+ * Get collection items
+ *
* @return Collection
*/
public function getItems()
diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php
index 0d64ecc9bff9..24822447ae91 100644
--- a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php
+++ b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php
@@ -91,11 +91,13 @@ public function __construct(
}
/**
+ * Prepare data
+ *
* @return $this
*/
protected function _prepareData()
{
- $product = $this->_coreRegistry->registry('product');
+ $product = $this->getProduct();
/* @var $product \Magento\Catalog\Model\Product */
$this->_itemCollection = $product->getUpSellProductCollection()->setPositionOrder()->addStoreFilter();
if ($this->moduleManager->isEnabled('Magento_Checkout')) {
@@ -121,6 +123,8 @@ protected function _prepareData()
}
/**
+ * Before to html handler
+ *
* @return $this
*/
protected function _beforeToHtml()
@@ -130,6 +134,8 @@ protected function _beforeToHtml()
}
/**
+ * Get items collection
+ *
* @return Collection
*/
public function getItemCollection()
@@ -145,6 +151,8 @@ public function getItemCollection()
}
/**
+ * Get collection items
+ *
* @return \Magento\Framework\DataObject[]
*/
public function getItems()
@@ -156,6 +164,8 @@ public function getItems()
}
/**
+ * Get row count
+ *
* @return float
*/
public function getRowCount()
@@ -164,6 +174,8 @@ public function getRowCount()
}
/**
+ * Set column count
+ *
* @param string $columns
* @return $this
*/
@@ -176,6 +188,8 @@ public function setColumnCount($columns)
}
/**
+ * Get column count
+ *
* @return int
*/
public function getColumnCount()
@@ -184,6 +198,8 @@ public function getColumnCount()
}
/**
+ * Reset items iterator
+ *
* @return void
*/
public function resetItemsIterator()
@@ -193,6 +209,8 @@ public function resetItemsIterator()
}
/**
+ * Get iterable item
+ *
* @return mixed
*/
public function getIterableItem()
@@ -204,6 +222,7 @@ public function getIterableItem()
/**
* Set how many items we need to show in upsell block
+ *
* Notice: this parameter will be also applied
*
* @param string $type
@@ -219,6 +238,8 @@ public function setItemLimit($type, $limit)
}
/**
+ * Get item limit
+ *
* @param string $type
* @return array|int
*/
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php
index 5089b37f90c5..a5be6223bee7 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php
@@ -8,6 +8,9 @@
use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+/**
+ * Controller for category listing
+ */
class Index extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpGetActionInterface
{
/**
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php
index bbef1de28e5b..09eacbbf0731 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php
@@ -6,8 +6,10 @@
namespace Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Backend\App\Action\Context;
use Magento\Catalog\Api\AttributeSetRepositoryInterface;
use Magento\Catalog\Api\Data\ProductAttributeInterface;
+use Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Eav\Api\AttributeGroupRepositoryInterface;
use Magento\Eav\Api\AttributeManagementInterface;
use Magento\Eav\Api\AttributeRepositoryInterface;
@@ -16,8 +18,14 @@
use Magento\Eav\Api\Data\AttributeInterface;
use Magento\Eav\Api\Data\AttributeSetInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
+use Magento\Framework\App\Action\HttpPostActionInterface;
+use Magento\Framework\Controller\Result\Json;
+use Magento\Framework\Controller\Result\JsonFactory;
+use Magento\Framework\DataObject;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\App\ObjectManager;
use Psr\Log\LoggerInterface;
+use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Api\ExtensionAttributesFactory;
/**
@@ -25,10 +33,10 @@
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class AddAttributeToTemplate extends \Magento\Catalog\Controller\Adminhtml\Product
+class AddAttributeToTemplate extends Product implements HttpPostActionInterface
{
/**
- * @var \Magento\Framework\Controller\Result\JsonFactory
+ * @var JsonFactory
*/
protected $resultJsonFactory;
@@ -75,33 +83,34 @@ class AddAttributeToTemplate extends \Magento\Catalog\Controller\Adminhtml\Produ
/**
* Constructor
*
- * @param \Magento\Backend\App\Action\Context $context
+ * @param Context $context
* @param Builder $productBuilder
- * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
- * @param \Magento\Eav\Api\Data\AttributeGroupInterfaceFactory|null $attributeGroupFactory
+ * @param JsonFactory $resultJsonFactory
+ * @param AttributeGroupInterfaceFactory|null $attributeGroupFactory
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
+ * @SuppressWarnings(PHPMD.LongVariable)
*/
public function __construct(
- \Magento\Backend\App\Action\Context $context,
- \Magento\Catalog\Controller\Adminhtml\Product\Builder $productBuilder,
- \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
- \Magento\Eav\Api\Data\AttributeGroupInterfaceFactory $attributeGroupFactory = null
+ Context $context,
+ Builder $productBuilder,
+ JsonFactory $resultJsonFactory,
+ AttributeGroupInterfaceFactory $attributeGroupFactory = null
) {
parent::__construct($context, $productBuilder);
$this->resultJsonFactory = $resultJsonFactory;
- $this->attributeGroupFactory = $attributeGroupFactory ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Eav\Api\Data\AttributeGroupInterfaceFactory::class);
+ $this->attributeGroupFactory = $attributeGroupFactory ?: ObjectManager::getInstance()
+ ->get(AttributeGroupInterfaceFactory::class);
}
/**
* Add attribute to attribute set
*
- * @return \Magento\Framework\Controller\Result\Json
+ * @return Json
*/
public function execute()
{
$request = $this->getRequest();
- $response = new \Magento\Framework\DataObject();
+ $response = new DataObject();
$response->setError(false);
try {
@@ -124,12 +133,12 @@ public function execute()
->getItems();
if (!$attributeGroupItems) {
- throw new \Magento\Framework\Exception\NoSuchEntityException;
+ throw new NoSuchEntityException;
}
/** @var AttributeGroupInterface $attributeGroup */
$attributeGroup = reset($attributeGroupItems);
- } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+ } catch (NoSuchEntityException $e) {
/** @var AttributeGroupInterface $attributeGroup */
$attributeGroup = $this->attributeGroupFactory->create();
}
@@ -176,101 +185,114 @@ public function execute()
* Adding basic filters
*
* @return SearchCriteriaBuilder
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
*/
private function getBasicAttributeSearchCriteriaBuilder()
{
- $attributeIds = (array)$this->getRequest()->getParam('attributeIds', []);
+ $attributeIds = (array) $this->getRequest()->getParam('attributeIds', []);
if (empty($attributeIds['selected'])) {
throw new LocalizedException(__('Attributes were missing and must be specified.'));
}
return $this->getSearchCriteriaBuilder()
- ->addFilter('attribute_set_id', new \Zend_Db_Expr('null'), 'is')
->addFilter('attribute_id', [$attributeIds['selected']], 'in');
}
/**
+ * Get AttributeRepositoryInterface
+ *
* @return AttributeRepositoryInterface
*/
private function getAttributeRepository()
{
if (null === $this->attributeRepository) {
- $this->attributeRepository = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Eav\Api\AttributeRepositoryInterface::class);
+ $this->attributeRepository = ObjectManager::getInstance()
+ ->get(AttributeRepositoryInterface::class);
}
return $this->attributeRepository;
}
/**
+ * Get AttributeSetRepositoryInterface
+ *
* @return AttributeSetRepositoryInterface
*/
private function getAttributeSetRepository()
{
if (null === $this->attributeSetRepository) {
- $this->attributeSetRepository = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Catalog\Api\AttributeSetRepositoryInterface::class);
+ $this->attributeSetRepository = ObjectManager::getInstance()
+ ->get(AttributeSetRepositoryInterface::class);
}
return $this->attributeSetRepository;
}
/**
+ * Get AttributeGroupInterface
+ *
* @return AttributeGroupRepositoryInterface
*/
private function getAttributeGroupRepository()
{
if (null === $this->attributeGroupRepository) {
- $this->attributeGroupRepository = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Eav\Api\AttributeGroupRepositoryInterface::class);
+ $this->attributeGroupRepository = ObjectManager::getInstance()
+ ->get(AttributeGroupRepositoryInterface::class);
}
return $this->attributeGroupRepository;
}
/**
+ * Get SearchCriteriaBuilder
+ *
* @return SearchCriteriaBuilder
*/
private function getSearchCriteriaBuilder()
{
if (null === $this->searchCriteriaBuilder) {
- $this->searchCriteriaBuilder = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Framework\Api\SearchCriteriaBuilder::class);
+ $this->searchCriteriaBuilder = ObjectManager::getInstance()
+ ->get(SearchCriteriaBuilder::class);
}
return $this->searchCriteriaBuilder;
}
/**
+ * Get AttributeManagementInterface
+ *
* @return AttributeManagementInterface
*/
private function getAttributeManagement()
{
if (null === $this->attributeManagement) {
- $this->attributeManagement = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Eav\Api\AttributeManagementInterface::class);
+ $this->attributeManagement = ObjectManager::getInstance()
+ ->get(AttributeManagementInterface::class);
}
return $this->attributeManagement;
}
/**
+ * Get LoggerInterface
+ *
* @return LoggerInterface
*/
private function getLogger()
{
if (null === $this->logger) {
- $this->logger = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Psr\Log\LoggerInterface::class);
+ $this->logger = ObjectManager::getInstance()
+ ->get(LoggerInterface::class);
}
return $this->logger;
}
/**
+ * Get ExtensionAttributesFactory.
+ *
* @return ExtensionAttributesFactory
*/
private function getExtensionAttributesFactory()
{
if (null === $this->extensionAttributesFactory) {
- $this->extensionAttributesFactory = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Framework\Api\ExtensionAttributesFactory::class);
+ $this->extensionAttributesFactory = ObjectManager::getInstance()
+ ->get(ExtensionAttributesFactory::class);
}
return $this->extensionAttributesFactory;
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
index 49e601357c60..39ed11b1806c 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
@@ -197,12 +197,12 @@ public function execute()
$attributeCode = $attributeCode ?: $this->generateCode($this->getRequest()->getParam('frontend_label')[0]);
if (strlen($attributeCode) > 0) {
$validatorAttrCode = new \Zend_Validate_Regex(
- ['pattern' => '/^[a-z\x{600}-\x{6FF}][a-z\x{600}-\x{6FF}_0-9]{0,30}$/u']
+ ['pattern' => '/^[a-zA-Z\x{600}-\x{6FF}][a-zA-Z\x{600}-\x{6FF}_0-9]{0,30}$/u']
);
if (!$validatorAttrCode->isValid($attributeCode)) {
$this->messageManager->addErrorMessage(
__(
- 'Attribute code "%1" is invalid. Please use only letters (a-z), ' .
+ 'Attribute code "%1" is invalid. Please use only letters (a-z or A-Z), ' .
'numbers (0-9) or underscore(_) in this field, first character should be a letter.',
$attributeCode
)
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
index 8a9d0c9b612c..381ca5d08d82 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
@@ -105,7 +105,7 @@ public function execute()
$attributeCode
);
- if ($attribute->getId() && !$attributeId) {
+ if ($attribute->getId() && !$attributeId || $attributeCode === 'product_type') {
$message = strlen($this->getRequest()->getParam('attribute_code'))
? __('An attribute with this code already exists.')
: __('An attribute with the same code (%1) already exists.', $attributeCode);
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare.php b/app/code/Magento/Catalog/Controller/Product/Compare.php
index 1ee146e5aaa7..084a82f87d64 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare.php
@@ -6,6 +6,7 @@
namespace Magento\Catalog\Controller\Product;
use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Data\Form\FormKey\Validator;
use Magento\Framework\View\Result\PageFactory;
@@ -15,7 +16,7 @@
* @SuppressWarnings(PHPMD.LongVariable)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-abstract class Compare extends \Magento\Framework\App\Action\Action
+abstract class Compare extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface
{
/**
* Customer id
@@ -139,4 +140,15 @@ public function setCustomerId($customerId)
$this->_customerId = $customerId;
return $this;
}
+
+ /**
+ * @inheritdoc
+ */
+ public function execute()
+ {
+ $resultRedirect = $this->resultRedirectFactory->create();
+ $resultRedirect->setPath('catalog/product_compare');
+
+ return $resultRedirect;
+ }
}
diff --git a/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php b/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php
index 7cc3eb9e3d2d..25f6d0c32368 100644
--- a/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php
+++ b/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php
@@ -12,6 +12,8 @@
use Magento\Store\Model\Store;
/**
+ * Cron job for removing outdated prices.
+ *
* Cron operation is responsible for deleting all product prices on WEBSITE level
* in case 'Catalog Price Scope' configuration parameter is set to GLOBAL.
*/
@@ -76,7 +78,7 @@ public function execute()
/**
* Checks if price scope config option explicitly equal to global value.
*
- * Such strict comparision is required to prevent price deleting when
+ * Such strict comparison is required to prevent price deleting when
* price scope config option is null for some reason.
*
* @return bool
diff --git a/app/code/Magento/Catalog/Helper/Product/Compare.php b/app/code/Magento/Catalog/Helper/Product/Compare.php
index 90d98874e00c..d6d35c5c76dd 100644
--- a/app/code/Magento/Catalog/Helper/Product/Compare.php
+++ b/app/code/Magento/Catalog/Helper/Product/Compare.php
@@ -166,7 +166,15 @@ public function getListUrl()
*/
public function getPostDataParams($product)
{
- return $this->postHelper->getPostData($this->getAddUrl(), ['product' => $product->getId()]);
+ $params = ['product' => $product->getId()];
+ $requestingPageUrl = $this->_getRequest()->getParam('requesting_page_url');
+
+ if (!empty($requestingPageUrl)) {
+ $encodedUrl = $this->urlEncoder->encode($requestingPageUrl);
+ $params[\Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED] = $encodedUrl;
+ }
+
+ return $this->postHelper->getPostData($this->getAddUrl(), $params);
}
/**
diff --git a/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php b/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php
index f22c6903a230..4ea06d4e34d7 100644
--- a/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php
+++ b/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php
@@ -6,7 +6,6 @@
namespace Magento\Catalog\Model\Category\Link;
use Magento\Catalog\Api\Data\CategoryLinkInterface;
-use Magento\Catalog\Model\Indexer\Product\Category;
use Magento\Framework\EntityManager\Operation\ExtensionInterface;
/**
@@ -40,6 +39,8 @@ public function __construct(
}
/**
+ * Execute
+ *
* @param object $entity
* @param array $arguments
* @return object
@@ -78,6 +79,8 @@ public function execute($entity, $arguments = [])
}
/**
+ * Get category links positions
+ *
* @param object $entity
* @return array
*/
@@ -106,27 +109,19 @@ private function getCategoryLinksPositions($entity)
*/
private function mergeCategoryLinks($newCategoryPositions, $oldCategoryPositions)
{
- $result = [];
if (empty($newCategoryPositions)) {
- return $result;
+ return [];
}
+ $categoryPositions = array_combine(array_column($oldCategoryPositions, 'category_id'), $oldCategoryPositions);
foreach ($newCategoryPositions as $newCategoryPosition) {
- $key = array_search(
- $newCategoryPosition['category_id'],
- array_column($oldCategoryPositions, 'category_id')
- );
-
- if ($key === false) {
- $result[] = $newCategoryPosition;
- } elseif (isset($oldCategoryPositions[$key])
- && $oldCategoryPositions[$key]['position'] != $newCategoryPosition['position']
- ) {
- $result[] = $newCategoryPositions[$key];
- unset($oldCategoryPositions[$key]);
+ $categoryId = $newCategoryPosition['category_id'];
+ if (!isset($categoryPositions[$categoryId])) {
+ $categoryPositions[$categoryId] = ['category_id' => $categoryId];
}
+ $categoryPositions[$categoryId]['position'] = $newCategoryPosition['position'];
}
- $result = array_merge($result, $oldCategoryPositions);
+ $result = array_values($categoryPositions);
return $result;
}
diff --git a/app/code/Magento/Catalog/Model/Category/Product/PositionResolver.php b/app/code/Magento/Catalog/Model/Category/Product/PositionResolver.php
index 1e07c0cdd924..44bf153f8369 100644
--- a/app/code/Magento/Catalog/Model/Category/Product/PositionResolver.php
+++ b/app/code/Magento/Catalog/Model/Category/Product/PositionResolver.php
@@ -43,6 +43,8 @@ public function getPositions(int $categoryId): array
$categoryId
)->order(
'ccp.position ' . \Magento\Framework\DB\Select::SQL_ASC
+ )->order(
+ 'ccp.product_id ' . \Magento\Framework\DB\Select::SQL_DESC
);
return array_flip($connection->fetchCol($select));
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php
index 6762602aecaf..ad734b96d59d 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php
@@ -118,9 +118,10 @@ public function removeDisabledProducts(array &$ids, $storeId)
private function getSelectForProducts(array $ids)
{
$productTable = $this->productIndexerHelper->getTable('catalog_product_entity');
- $select = $this->connection->select()->from($productTable)
+ $select = $this->connection->select()
+ ->from(['product_table' => $productTable])
->columns('entity_id')
- ->where('entity_id IN(?)', $ids);
+ ->where('product_table.entity_id IN(?)', $ids);
return $select;
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php
index 466ba746fa10..a669fb73f64f 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php
@@ -9,6 +9,7 @@
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Store\Model\Store;
/**
* Class Indexer
@@ -84,7 +85,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
[
'entity_id' => 'e.entity_id',
'attribute_id' => 't.attribute_id',
- 'value' => $this->_connection->getIfNullSql('`t2`.`value`', '`t`.`value`'),
+ 'value' => 't.value'
]
);
@@ -99,32 +100,30 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
sprintf('e.%s = t.%s ', $linkField, $linkField) . $this->_connection->quoteInto(
' AND t.attribute_id IN (?)',
array_keys($ids)
- ) . ' AND t.store_id = 0',
- []
- )->joinLeft(
- ['t2' => $tableName],
- sprintf('t.%s = t2.%s ', $linkField, $linkField) .
- ' AND t.attribute_id = t2.attribute_id ' .
- $this->_connection->quoteInto(
- ' AND t2.store_id = ?',
- $storeId
- ),
+ ) . ' AND ' . $this->_connection->quoteInto('t.store_id IN(?)', [
+ Store::DEFAULT_STORE_ID,
+ $storeId
+ ]),
[]
)->where(
'e.entity_id = ' . $productId
- );
+ )->order('t.store_id ASC');
$cursor = $this->_connection->query($select);
while ($row = $cursor->fetch(\Zend_Db::FETCH_ASSOC)) {
$updateData[$ids[$row['attribute_id']]] = $row['value'];
$valueColumnName = $ids[$row['attribute_id']] . $valueFieldSuffix;
if (isset($describe[$valueColumnName])) {
- $valueColumns[$row['value']] = $valueColumnName;
+ $valueColumns[$row['attribute_id']] = [
+ 'value' => $row['value'],
+ 'column_name' => $valueColumnName
+ ];
}
}
//Update not simple attributes (eg. dropdown)
if (!empty($valueColumns)) {
- $valueIds = array_keys($valueColumns);
+ $valueIds = array_column($valueColumns, 'value');
+ $optionIdToAttributeName = array_column($valueColumns, 'column_name', 'value');
$select = $this->_connection->select()->from(
['t' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')],
@@ -133,14 +132,14 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
$this->_connection->quoteInto('t.option_id IN (?)', $valueIds)
)->where(
$this->_connection->quoteInto('t.store_id IN(?)', [
- \Magento\Store\Model\Store::DEFAULT_STORE_ID,
+ Store::DEFAULT_STORE_ID,
$storeId
])
)
->order('t.store_id ASC');
$cursor = $this->_connection->query($select);
while ($row = $cursor->fetch(\Zend_Db::FETCH_ASSOC)) {
- $valueColumnName = $valueColumns[$row['option_id']];
+ $valueColumnName = $optionIdToAttributeName[$row['option_id']];
if (isset($describe[$valueColumnName])) {
$updateData[$valueColumnName] = $row['value'];
}
@@ -150,6 +149,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
$columnNames = array_keys($columns);
$columnNames[] = 'attribute_set_id';
$columnNames[] = 'type_id';
+ $columnNames[] = $linkField;
$select->from(
['e' => $entityTableName],
$columnNames
@@ -159,6 +159,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
$cursor = $this->_connection->query($select);
$row = $cursor->fetch(\Zend_Db::FETCH_ASSOC);
if (!empty($row)) {
+ $linkFieldId = $linkField;
foreach ($row as $columnName => $value) {
$updateData[$columnName] = $value;
}
@@ -170,7 +171,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
if (!empty($updateData)) {
$updateData += ['entity_id' => $productId];
if ($linkField !== $metadata->getIdentifierField()) {
- $updateData += [$linkField => $productId];
+ $updateData += [$linkField => $linkFieldId];
}
$updateFields = [];
foreach ($updateData as $key => $value) {
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php
index d9bfc96b7817..64a7c4be4e03 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php
@@ -96,15 +96,17 @@ public function execute($id = null)
/* @var $status \Magento\Eav\Model\Entity\Attribute */
$status = $this->_productIndexerHelper->getAttribute(ProductInterface::STATUS);
$statusTable = $status->getBackend()->getTable();
+ $catalogProductEntityTable = $this->_productIndexerHelper->getTable('catalog_product_entity');
$statusConditions = [
- 'store_id IN(0,' . (int)$store->getId() . ')',
- 'attribute_id = ' . (int)$status->getId(),
- $linkField . ' = ' . (int)$id,
+ 's.store_id IN(0,' . (int)$store->getId() . ')',
+ 's.attribute_id = ' . (int)$status->getId(),
+ 'e.entity_id = ' . (int)$id,
];
$select = $this->_connection->select();
- $select->from($statusTable, ['value'])
+ $select->from(['e' => $catalogProductEntityTable], ['s.value'])
->where(implode(' AND ', $statusConditions))
- ->order('store_id DESC')
+ ->joinLeft(['s' => $statusTable], "e.{$linkField} = s.{$linkField}", [])
+ ->order('s.store_id DESC')
->limit(1);
$result = $this->_connection->query($select);
$status = $result->fetchColumn(0);
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php
index a32379b8c0a6..a3d958ea537e 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php
@@ -7,6 +7,9 @@
use Magento\Catalog\Model\Indexer\Product\Flat\Table\BuilderInterfaceFactory;
+/**
+ * Class TableBuilder
+ */
class TableBuilder
{
/**
@@ -137,13 +140,23 @@ protected function _createTemporaryTable($tableName, array $columns, $valueField
);
$flatColumns = $this->_productIndexerHelper->getFlatColumns();
- $temporaryTableBuilder->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER);
+ $temporaryTableBuilder->addColumn(
+ 'entity_id',
+ \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
+ null,
+ ['unsigned'=>true]
+ );
$temporaryTableBuilder->addColumn('type_id', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT);
$temporaryTableBuilder->addColumn('attribute_set_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER);
- $valueTemporaryTableBuilder->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER);
+ $valueTemporaryTableBuilder->addColumn(
+ 'entity_id',
+ \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
+ null,
+ ['unsigned'=>true]
+ );
/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
foreach ($columns as $columnName => $attribute) {
@@ -198,9 +211,10 @@ protected function _getTemporaryTableName($tableName)
* Fill temporary entity table
*
* @param string $tableName
- * @param array $columns
- * @param array $changedIds
+ * @param array $columns
+ * @param array $changedIds
* @return void
+ * @throws \Exception
*/
protected function _fillTemporaryEntityTable($tableName, array $columns, array $changedIds = [])
{
@@ -244,11 +258,12 @@ protected function _addPrimaryKeyToTable($tableName, $columnName = 'entity_id')
* Fill temporary table by data from products EAV attributes by type
*
* @param string $tableName
- * @param array $tableColumns
- * @param array $changedIds
+ * @param array $tableColumns
+ * @param array $changedIds
* @param string $valueFieldSuffix
* @param int $storeId
* @return void
+ * @throws \Exception
*/
protected function _fillTemporaryTable(
$tableName,
@@ -345,6 +360,8 @@ protected function _fillTemporaryTable(
}
/**
+ * Get Metadata Pool
+ *
* @return \Magento\Framework\EntityManager\MetadataPool
* @deprecated 101.1.0
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php
index 248d8ed22125..3e7cc59155dd 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php
@@ -135,12 +135,12 @@ private function getAdditionalFields(array $objectArray): array
* Check whether price has percentage value.
*
* @param array $priceRow
- * @return int|null
+ * @return float|null
*/
- private function getPercentage(array $priceRow): ?int
+ private function getPercentage(array $priceRow): ?float
{
return isset($priceRow['percentage_value']) && is_numeric($priceRow['percentage_value'])
- ? (int)$priceRow['percentage_value']
+ ? (float)$priceRow['percentage_value']
: null;
}
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php
index aef3e8758601..71808274873d 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php
@@ -138,12 +138,12 @@ private function getAdditionalFields(array $objectArray): array
* Check whether price has percentage value.
*
* @param array $priceRow
- * @return int|null
+ * @return float|null
*/
- private function getPercentage(array $priceRow): ?int
+ private function getPercentage(array $priceRow): ?float
{
return isset($priceRow['percentage_value']) && is_numeric($priceRow['percentage_value'])
- ? (int)$priceRow['percentage_value']
+ ? (float)$priceRow['percentage_value']
: null;
}
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php b/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php
index f2039a5002dc..8b638feafaaf 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php
@@ -8,6 +8,9 @@
use Magento\Framework\Exception\InputException;
+/**
+ * Option management model for product attribute.
+ */
class OptionManagement implements \Magento\Catalog\Api\ProductAttributeOptionManagementInterface
{
/**
@@ -25,7 +28,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getItems($attributeCode)
{
@@ -36,7 +39,7 @@ public function getItems($attributeCode)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function add($attributeCode, $option)
{
@@ -47,7 +50,7 @@ public function add($attributeCode, $option)
/** @var \Magento\Eav\Api\Data\AttributeOptionInterface $attributeOption */
$attributeOption = $attributeOption->getLabel();
});
- if (in_array($option->getLabel(), $currentOptions)) {
+ if (in_array($option->getLabel(), $currentOptions, true)) {
return false;
}
}
@@ -59,7 +62,7 @@ public function add($attributeCode, $option)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete($attributeCode, $optionId)
{
diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
index 1bddd2d07cd8..3ee064670a46 100644
--- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
+++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
@@ -97,7 +97,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get(array $skus)
{
@@ -107,7 +107,7 @@ public function get(array $skus)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function update(array $prices)
{
@@ -128,7 +128,7 @@ public function update(array $prices)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function replace(array $prices)
{
@@ -144,7 +144,7 @@ public function replace(array $prices)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(array $prices)
{
@@ -171,16 +171,17 @@ private function getExistingPrices(array $skus, $groupBySku = false)
$ids = $this->retrieveAffectedIds($skus);
$rawPrices = $this->tierPricePersistence->get($ids);
$prices = [];
-
- $linkField = $this->tierPricePersistence->getEntityLinkField();
- $skuByIdLookup = $this->buildSkuByIdLookup($skus);
- foreach ($rawPrices as $rawPrice) {
- $sku = $skuByIdLookup[$rawPrice[$linkField]];
- $price = $this->tierPriceFactory->create($rawPrice, $sku);
- if ($groupBySku) {
- $prices[$sku][] = $price;
- } else {
- $prices[] = $price;
+ if ($rawPrices) {
+ $linkField = $this->tierPricePersistence->getEntityLinkField();
+ $skuByIdLookup = $this->buildSkuByIdLookup($skus);
+ foreach ($rawPrices as $rawPrice) {
+ $sku = $skuByIdLookup[$rawPrice[$linkField]];
+ $price = $this->tierPriceFactory->create($rawPrice, $sku);
+ if ($groupBySku) {
+ $prices[$sku][] = $price;
+ } else {
+ $prices[] = $price;
+ }
}
}
diff --git a/app/code/Magento/Catalog/Model/ProductIdLocator.php b/app/code/Magento/Catalog/Model/ProductIdLocator.php
index 2d9af6829ad6..2d382164f264 100644
--- a/app/code/Magento/Catalog/Model/ProductIdLocator.php
+++ b/app/code/Magento/Catalog/Model/ProductIdLocator.php
@@ -37,23 +37,43 @@ class ProductIdLocator implements \Magento\Catalog\Model\ProductIdLocatorInterfa
*/
private $idsBySku = [];
+ /**
+ * Batch size to iterate collection
+ *
+ * @var int
+ */
+ private $batchSize;
+
/**
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
* @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory
- * @param string $limitIdsBySkuValues
+ * @param string $idsLimit
+ * @param int $batchSize defines how many items can be processed by one iteration
*/
public function __construct(
\Magento\Framework\EntityManager\MetadataPool $metadataPool,
\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory,
- $idsLimit
+ $idsLimit,
+ int $batchSize = 5000
) {
$this->metadataPool = $metadataPool;
$this->collectionFactory = $collectionFactory;
$this->idsLimit = (int)$idsLimit;
+ $this->batchSize = $batchSize;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
+ * Load product items by provided products SKUs.
+ * Products collection will be iterated by pages with the $this->batchSize as a page size (for a cases when to many
+ * products SKUs were provided in parameters.
+ * Loaded products will be chached in the $this->idsBySku variable, but in the end of the method these storage will
+ * be truncated to $idsLimit quantity.
+ * As a result array with the products data will be returned with the following scheme:
+ * $data['product_sku']['link_field_value' => 'product_type']
+ *
+ * @throws \Exception
*/
public function retrieveProductIdsBySkus(array $skus)
{
@@ -72,8 +92,16 @@ public function retrieveProductIdsBySkus(array $skus)
$linkField = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
->getLinkField();
- foreach ($collection as $item) {
- $this->idsBySku[strtolower(trim($item->getSku()))][$item->getData($linkField)] = $item->getTypeId();
+ $collection->setPageSize($this->batchSize);
+ $pages = $collection->getLastPageNumber();
+ for ($currentPage = 1; $currentPage <= $pages; $currentPage++) {
+ $collection->setCurPage($currentPage);
+ foreach ($collection->getItems() as $item) {
+ $sku = strtolower(trim($item->getSku()));
+ $itemIdentifier = $item->getData($linkField);
+ $this->idsBySku[$sku][$itemIdentifier] = $item->getTypeId();
+ }
+ $collection->clear();
}
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
index 5b420c33b318..90f55cd44bdb 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
@@ -486,8 +486,20 @@ public function getProductsPosition($category)
$this->getCategoryProductTable(),
['product_id', 'position']
)->where(
- 'category_id = :category_id'
+ "{$this->getTable('catalog_category_product')}.category_id = ?",
+ $category->getId()
);
+ $websiteId = $category->getStore()->getWebsiteId();
+ if ($websiteId) {
+ $select->join(
+ ['product_website' => $this->getTable('catalog_product_website')],
+ "product_website.product_id = {$this->getTable('catalog_category_product')}.product_id",
+ []
+ )->where(
+ 'product_website.website_id = ?',
+ $websiteId
+ );
+ }
$bind = ['category_id' => (int)$category->getId()];
return $this->getConnection()->fetchPairs($select, $bind);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php
index 46bb74513b59..4562aa0b0e75 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php
@@ -322,11 +322,7 @@ public function loadProductCount($items, $countRegular = true, $countAnchor = tr
['e' => $this->getTable('catalog_category_entity')],
'main_table.category_id=e.entity_id',
[]
- )->where(
- 'e.entity_id = :entity_id'
- )->orWhere(
- 'e.path LIKE :c_path'
- );
+ )->where('e.entity_id = :entity_id OR e.path LIKE :c_path');
if ($websiteId) {
$select->join(
['w' => $this->getProductWebsiteTable()],
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php
index 9db2c8248ce5..05950531e217 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php
@@ -699,8 +699,20 @@ public function getProductsPosition($category)
$this->getTable('catalog_category_product'),
['product_id', 'position']
)->where(
- 'category_id = :category_id'
+ "{$this->getTable('catalog_category_product')}.category_id = ?",
+ $category->getId()
);
+ $websiteId = $category->getStore()->getWebsiteId();
+ if ($websiteId) {
+ $select->join(
+ ['product_website' => $this->getTable('catalog_product_website')],
+ "product_website.product_id = {$this->getTable('catalog_category_product')}.product_id",
+ []
+ )->where(
+ 'product_website.website_id = ?',
+ $websiteId
+ );
+ }
$bind = ['category_id' => (int)$category->getId()];
return $this->getConnection()->fetchPairs($select, $bind);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php
index b54c19a11150..cf5760b0c33a 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php
@@ -93,6 +93,8 @@ public function saveCategoryLinks(ProductInterface $product, array $categoryLink
}
/**
+ * Get category link metadata
+ *
* @return \Magento\Framework\EntityManager\EntityMetadataInterface
*/
private function getCategoryLinkMetadata()
@@ -114,16 +116,22 @@ private function getCategoryLinkMetadata()
private function processCategoryLinks($newCategoryPositions, &$oldCategoryPositions)
{
$result = ['changed' => [], 'updated' => []];
+
+ $oldCategoryPositions = array_values($oldCategoryPositions);
foreach ($newCategoryPositions as $newCategoryPosition) {
- $key = array_search(
- $newCategoryPosition['category_id'],
- array_column($oldCategoryPositions, 'category_id')
- );
+ $key = false;
+
+ foreach ($oldCategoryPositions as $oldKey => $oldCategoryPosition) {
+ if ((int)$oldCategoryPosition['category_id'] === (int)$newCategoryPosition['category_id']) {
+ $key = $oldKey;
+ break;
+ }
+ }
if ($key === false) {
$result['changed'][] = $newCategoryPosition;
} elseif ($oldCategoryPositions[$key]['position'] != $newCategoryPosition['position']) {
- $result['updated'][] = $newCategoryPositions[$key];
+ $result['updated'][] = $newCategoryPosition;
unset($oldCategoryPositions[$key]);
}
}
@@ -132,6 +140,8 @@ private function processCategoryLinks($newCategoryPositions, &$oldCategoryPositi
}
/**
+ * Update category links
+ *
* @param ProductInterface $product
* @param array $insertLinks
* @param bool $insert
@@ -175,6 +185,8 @@ private function updateCategoryLinks(ProductInterface $product, array $insertLin
}
/**
+ * Delete category links
+ *
* @param ProductInterface $product
* @param array $deleteLinks
* @return array
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
index 14ae38667d87..bd314c0192d3 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
@@ -290,6 +290,11 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac
*/
private $dimensionFactory;
+ /**
+ * @var \Magento\Framework\DataObject
+ */
+ private $emptyItem;
+
/**
* Collection constructor
* @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
@@ -550,7 +555,10 @@ protected function _prepareStaticFields()
*/
public function getNewEmptyItem()
{
- $object = parent::getNewEmptyItem();
+ if (null === $this->emptyItem) {
+ $this->emptyItem = parent::getNewEmptyItem();
+ }
+ $object = clone $this->emptyItem;
if ($this->isEnabledFlat()) {
$object->setIdFieldName($this->getEntity()->getIdFieldName());
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php
index 3a61e7e36445..7730d7cc9a7f 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php
@@ -7,6 +7,7 @@
use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Api\Data\ProductAttributeInterface;
/**
* Catalog Product Eav Select and Multiply Select Attributes Indexer resource model
@@ -24,6 +25,16 @@ class Source extends AbstractEav
*/
protected $_resourceHelper;
+ /**
+ * @var \Magento\Eav\Api\AttributeRepositoryInterface
+ */
+ private $attributeRepository;
+
+ /**
+ * @var \Magento\Framework\Api\SearchCriteriaBuilder
+ */
+ private $criteriaBuilder;
+
/**
* Construct
*
@@ -33,6 +44,8 @@ class Source extends AbstractEav
* @param \Magento\Framework\Event\ManagerInterface $eventManager
* @param \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper
* @param null|string $connectionName
+ * @param \Magento\Eav\Api\AttributeRepositoryInterface|null $attributeRepository
+ * @param \Magento\Framework\Api\SearchCriteriaBuilder|null $criteriaBuilder
*/
public function __construct(
\Magento\Framework\Model\ResourceModel\Db\Context $context,
@@ -40,7 +53,9 @@ public function __construct(
\Magento\Eav\Model\Config $eavConfig,
\Magento\Framework\Event\ManagerInterface $eventManager,
\Magento\Catalog\Model\ResourceModel\Helper $resourceHelper,
- $connectionName = null
+ $connectionName = null,
+ \Magento\Eav\Api\AttributeRepositoryInterface $attributeRepository = null,
+ \Magento\Framework\Api\SearchCriteriaBuilder $criteriaBuilder = null
) {
parent::__construct(
$context,
@@ -50,6 +65,12 @@ public function __construct(
$connectionName
);
$this->_resourceHelper = $resourceHelper;
+ $this->attributeRepository = $attributeRepository
+ ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\Eav\Api\AttributeRepositoryInterface::class);
+ $this->criteriaBuilder = $criteriaBuilder
+ ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\Framework\Api\SearchCriteriaBuilder::class);
}
/**
@@ -234,6 +255,10 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu
$options[$row['attribute_id']][$row['option_id']] = true;
}
+ // Retrieve any custom source model options
+ $sourceModelOptions = $this->getMultiSelectAttributeWithSourceModels($attrIds);
+ $options = array_replace_recursive($options, $sourceModelOptions);
+
// prepare get multiselect values query
$productValueExpression = $connection->getCheckSql('pvs.value_id > 0', 'pvs.value', 'pvd.value');
$select = $connection->select()->from(
@@ -297,6 +322,39 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu
return $this;
}
+ /**
+ * Get options for multiselect attributes using custom source models
+ * Based on @maderlock's fix from:
+ * https://github.com/magento/magento2/issues/417#issuecomment-265146285
+ *
+ * @param array $attrIds
+ *
+ * @return array
+ */
+ private function getMultiSelectAttributeWithSourceModels($attrIds)
+ {
+ // Add options from custom source models
+ $this->criteriaBuilder
+ ->addFilter('attribute_id', $attrIds, 'in')
+ ->addFilter('source_model', true, 'notnull');
+ $criteria = $this->criteriaBuilder->create();
+ $attributes = $this->attributeRepository->getList(
+ ProductAttributeInterface::ENTITY_TYPE_CODE,
+ $criteria
+ )->getItems();
+
+ $options = [];
+ foreach ($attributes as $attribute) {
+ $sourceModelOptions = $attribute->getOptions();
+ // Add options to list used below
+ foreach ($sourceModelOptions as $option) {
+ $options[$attribute->getAttributeId()][$option->getValue()] = true;
+ }
+ }
+
+ return $options;
+ }
+
/**
* Save a data to temporary source index table
*
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
index 01b31430793c..2b5fbfbe6a79 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
@@ -121,7 +121,6 @@
-
@@ -313,4 +312,19 @@
+
+
+
+
+
+
+
+
+
+
+ {{amount}}
+ $grabProductTierPriceInput
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
index a077eced6d5d..8d16d35ab0dd 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
@@ -179,7 +179,6 @@
-
@@ -193,6 +192,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -213,4 +223,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index e5b23fe3a3c3..53a8b54da988 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -181,6 +181,9 @@
EavStockItem
CustomAttributeProductUrlKey
+
+ 1
+
Image1
1.00
@@ -477,4 +480,8 @@
EavStock1
CustomAttributeProductAttribute
+
+ Product With Long Name And Sku - But not too long
+ Product With Long Name And Sku - But not too long
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml
index 9349e188430f..f7d8abf8b2fe 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml
index c7d4cd16d788..aa752e0e2289 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml
@@ -15,6 +15,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
index 249610568aec..5791d0bfedab 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
@@ -167,6 +167,12 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
index d12233206ce4..59228d57502f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
@@ -30,5 +30,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
index 7ea74d791375..178e58ef2d64 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
@@ -28,7 +28,8 @@
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
index d38a8e4190c8..03f3e93bb30e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
@@ -16,9 +16,6 @@
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml
index 36d9dfeedc5e..545e7c10379b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml
@@ -269,4 +269,56 @@
grabTextFromMiniCartSubtotalField2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml
index a748635ac9a5..9e323745835c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml
@@ -140,6 +140,7 @@
userInput="$$createProduct1.name$$" stepKey="seeProductName4"/>
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml
index a8b2df1fcfa6..f283a040ced4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml
@@ -59,6 +59,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml
index 26dd94b19de6..29ed3af4f01d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml
@@ -32,16 +32,19 @@
-
-
+
+
+
+
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/Product/PositionResolverTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/Product/PositionResolverTest.php
index 1ff3a1bae5c2..7ad8b1a0ab3f 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Category/Product/PositionResolverTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/Product/PositionResolverTest.php
@@ -107,7 +107,7 @@ public function testGetPositions()
$this->select->expects($this->once())
->method('where')
->willReturnSelf();
- $this->select->expects($this->once())
+ $this->select->expects($this->exactly(2))
->method('order')
->willReturnSelf();
$this->select->expects($this->once())
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php
index c04428eadef0..e1e2816d4422 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php
@@ -53,9 +53,14 @@ public function testRemoveDeletedProducts()
{
$productsToDeleteIds = [1, 2];
$select = $this->createMock(\Magento\Framework\DB\Select::class);
- $select->expects($this->once())->method('from')->with('catalog_product_entity')->will($this->returnSelf());
+ $select->expects($this->once())
+ ->method('from')
+ ->with(['product_table' => 'catalog_product_entity'])
+ ->will($this->returnSelf());
$select->expects($this->once())->method('columns')->with('entity_id')->will($this->returnSelf());
- $select->expects($this->once())->method('where')->with('entity_id IN(?)', $productsToDeleteIds)
+ $select->expects($this->once())
+ ->method('where')
+ ->with('product_table.entity_id IN(?)', $productsToDeleteIds)
->will($this->returnSelf());
$products = [['entity_id' => 2]];
$statement = $this->createMock(\Zend_Db_Statement_Interface::class);
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php
index 85d8dd8a1491..11d07872fef9 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php
@@ -100,10 +100,10 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
$this->connection->expects($this->any())->method('select')->willReturn($selectMock);
- $selectMock->expects($this->any())->method('from')->with(
- $attributeTable,
- ['value']
- )->willReturnSelf();
+ $selectMock->method('from')
+ ->willReturnSelf();
+ $selectMock->method('joinLeft')
+ ->willReturnSelf();
$selectMock->expects($this->any())->method('where')->willReturnSelf();
$selectMock->expects($this->any())->method('order')->willReturnSelf();
$selectMock->expects($this->any())->method('limit')->willReturnSelf();
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
index c9288790ed6e..a97f2281125a 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
@@ -152,6 +152,30 @@ public function testGet()
$this->assertEquals(2, count($prices));
}
+ /**
+ * Test get method without tierprices.
+ *
+ * @return void
+ */
+ public function testGetWithoutTierPrices()
+ {
+ $skus = ['simple', 'virtual'];
+ $this->tierPriceValidator
+ ->expects($this->once())
+ ->method('validateSkus')
+ ->with($skus)
+ ->willReturn($skus);
+ $this->productIdLocator->expects($this->atLeastOnce())
+ ->method('retrieveProductIdsBySkus')
+ ->with(['simple', 'virtual'])
+ ->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]);
+ $this->tierPricePersistence->expects($this->once())->method('get')->willReturn([]);
+ $this->tierPricePersistence->expects($this->never())->method('getEntityLinkField');
+ $this->tierPriceFactory->expects($this->never())->method('create');
+ $prices = $this->tierPriceStorage->get($skus);
+ $this->assertEmpty($prices);
+ }
+
/**
* Test update method.
*
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php
index b730e12ca820..b9cb82274c80 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php
@@ -58,7 +58,16 @@ public function testRetrieveProductIdsBySkus()
{
$skus = ['sku_1', 'sku_2'];
$collection = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Collection::class)
- ->setMethods(['getIterator', 'addFieldToFilter'])
+ ->setMethods(
+ [
+ 'getItems',
+ 'addFieldToFilter',
+ 'setPageSize',
+ 'getLastPageNumber',
+ 'setCurPage',
+ 'clear'
+ ]
+ )
->disableOriginalConstructor()->getMock();
$product = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class)
->setMethods(['getSku', 'getData', 'getTypeId'])
@@ -69,7 +78,11 @@ public function testRetrieveProductIdsBySkus()
$this->collectionFactory->expects($this->once())->method('create')->willReturn($collection);
$collection->expects($this->once())->method('addFieldToFilter')
->with(\Magento\Catalog\Api\Data\ProductInterface::SKU, ['in' => $skus])->willReturnSelf();
- $collection->expects($this->once())->method('getIterator')->willReturn(new \ArrayIterator([$product]));
+ $collection->expects($this->atLeastOnce())->method('getItems')->willReturn([$product]);
+ $collection->expects($this->atLeastOnce())->method('setPageSize')->willReturnSelf();
+ $collection->expects($this->atLeastOnce())->method('getLastPageNumber')->willReturn(1);
+ $collection->expects($this->atLeastOnce())->method('setCurPage')->with(1)->willReturnSelf();
+ $collection->expects($this->atLeastOnce())->method('clear')->willReturnSelf();
$this->metadataPool
->expects($this->once())
->method('getMetadata')
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php
index 9e2b19660299..5a1a5906ec4b 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Product;
use Magento\Catalog\Model\ResourceModel\Product\CategoryLink;
@@ -129,9 +130,20 @@ public function testSaveCategoryLinks($newCategoryLinks, $dbCategoryLinks, $affe
);
}
+ $expectedResult = [];
+
+ foreach ($affectedIds as $type => $ids) {
+ $expectedResult = array_merge($expectedResult, $ids);
+ // Verify if the correct insert, update and/or delete actions are performed:
+ $this->setupExpectationsForConnection($type, $ids);
+ }
+
$actualResult = $this->model->saveCategoryLinks($product, $newCategoryLinks);
+
sort($actualResult);
- $this->assertEquals($affectedIds, $actualResult);
+ sort($expectedResult);
+
+ $this->assertEquals($expectedResult, $actualResult);
}
/**
@@ -151,7 +163,11 @@ public function getCategoryLinksDataProvider()
['category_id' => 3, 'position' => 10],
['category_id' => 4, 'position' => 20],
],
- [], // Nothing to update - data not changed
+ [
+ 'update' => [],
+ 'insert' => [],
+ 'delete' => [],
+ ],
],
[
[
@@ -162,7 +178,11 @@ public function getCategoryLinksDataProvider()
['category_id' => 3, 'position' => 10],
['category_id' => 4, 'position' => 20],
],
- [3, 4, 5], // 4 - updated position, 5 - added, 3 - deleted
+ [
+ 'update' => [4],
+ 'insert' => [5],
+ 'delete' => [3],
+ ],
],
[
[
@@ -173,7 +193,11 @@ public function getCategoryLinksDataProvider()
['category_id' => 3, 'position' => 10],
['category_id' => 4, 'position' => 20],
],
- [3, 4], // 3 - updated position, 4 - deleted
+ [
+ 'update' => [3],
+ 'insert' => [],
+ 'delete' => [4],
+ ],
],
[
[],
@@ -181,8 +205,80 @@ public function getCategoryLinksDataProvider()
['category_id' => 3, 'position' => 10],
['category_id' => 4, 'position' => 20],
],
- [3, 4], // 3, 4 - deleted
+ [
+ 'update' => [],
+ 'insert' => [],
+ 'delete' => [3, 4],
+ ],
],
+ [
+ [
+ ['category_id' => 3, 'position' => 10],
+ ['category_id' => 4, 'position' => 20],
+ ],
+ [
+ ['category_id' => 3, 'position' => 20], // swapped positions
+ ['category_id' => 4, 'position' => 10], // swapped positions
+ ],
+ [
+ 'update' => [3, 4],
+ 'insert' => [],
+ 'delete' => [],
+ ],
+ ]
];
}
+
+ /**
+ * @param $type
+ * @param $ids
+ */
+ private function setupExpectationsForConnection($type, $ids): void
+ {
+ switch ($type) {
+ case 'insert':
+ $this->connectionMock
+ ->expects($this->exactly(empty($ids) ? 0 : 1))
+ ->method('insertArray')
+ ->with(
+ $this->anything(),
+ $this->anything(),
+ $this->callback(function ($data) use ($ids) {
+ $foundIds = [];
+ foreach ($data as $row) {
+ $foundIds[] = $row['category_id'];
+ }
+ return $ids === $foundIds;
+ })
+ );
+ break;
+ case 'update':
+ $this->connectionMock
+ ->expects($this->exactly(empty($ids) ? 0 : 1))
+ ->method('insertOnDuplicate')
+ ->with(
+ $this->anything(),
+ $this->callback(function ($data) use ($ids) {
+ $foundIds = [];
+ foreach ($data as $row) {
+ $foundIds[] = $row['category_id'];
+ }
+ return $ids === $foundIds;
+ })
+ );
+ break;
+ case 'delete':
+ $this->connectionMock
+ ->expects($this->exactly(empty($ids) ? 0 : 1))
+ ->method('delete')
+ // Verify that the correct category ID's are touched:
+ ->with(
+ $this->anything(),
+ $this->callback(function ($data) use ($ids) {
+ return array_values($data)[1] === $ids;
+ })
+ );
+ break;
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
index dbbb3fb29513..bb39aa7f9db7 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
@@ -58,13 +58,18 @@ class CollectionTest extends \PHPUnit\Framework\TestCase
*/
private $storeManager;
+ /**
+ * @var \Magento\Framework\Data\Collection\EntityFactory|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $entityFactory;
+
/**
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function setUp()
{
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
- $entityFactory = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class);
+ $this->entityFactory = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class);
$logger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
@@ -168,7 +173,7 @@ protected function setUp()
$this->collection = $this->objectManager->getObject(
\Magento\Catalog\Model\ResourceModel\Product\Collection::class,
[
- 'entityFactory' => $entityFactory,
+ 'entityFactory' => $this->entityFactory,
'logger' => $logger,
'fetchStrategy' => $fetchStrategy,
'eventManager' => $eventManager,
@@ -379,4 +384,20 @@ public function testAddTierPriceData()
$this->assertSame($this->collection, $this->collection->addTierPriceData());
}
+
+ /**
+ * Test for getNewEmptyItem() method
+ *
+ * @return void
+ */
+ public function testGetNewEmptyItem()
+ {
+ $item = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->entityFactory->expects($this->once())->method('create')->willReturn($item);
+ $firstItem = $this->collection->getNewEmptyItem();
+ $secondItem = $this->collection->getNewEmptyItem();
+ $this->assertEquals($firstItem, $secondItem);
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php
index 12bc9acfa4c5..009cd690d4cd 100644
--- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php
@@ -15,6 +15,7 @@
use Magento\Catalog\Helper\ImageFactory;
use Magento\Catalog\Api\Data\ProductRender\ImageInterface;
use Magento\Catalog\Helper\Image as ImageHelper;
+use Magento\Framework\View\DesignLoader;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -33,6 +34,9 @@ class ImageTest extends \PHPUnit\Framework\TestCase
/** @var DesignInterface | \PHPUnit_Framework_MockObject_MockObject */
private $design;
+ /** @var DesignLoader | \PHPUnit_Framework_MockObject_MockObject*/
+ private $designLoader;
+
/** @var Image */
private $model;
@@ -60,13 +64,15 @@ public function setUp()
->getMock();
$this->storeManager = $this->createMock(StoreManagerInterface::class);
$this->design = $this->createMock(DesignInterface::class);
+ $this->designLoader = $this->createMock(DesignLoader::class);
$this->model = new Image(
$this->imageFactory,
$this->state,
$this->storeManager,
$this->design,
$this->imageInterfaceFactory,
- $this->imageCodes
+ $this->imageCodes,
+ $this->designLoader
);
}
diff --git a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
index cbc67fee8a5a..1903bcd14483 100644
--- a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
+++ b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
@@ -6,6 +6,8 @@
namespace Magento\Catalog\Ui\Component;
/**
+ * Column Factory
+ *
* @api
* @since 100.0.2
*/
@@ -47,10 +49,14 @@ public function __construct(\Magento\Framework\View\Element\UiComponentFactory $
}
/**
+ * Create Factory
+ *
* @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute
* @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context
* @param array $config
+ *
* @return \Magento\Ui\Component\Listing\Columns\ColumnInterface
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function create($attribute, $context, array $config = [])
{
@@ -82,7 +88,10 @@ public function create($attribute, $context, array $config = [])
}
/**
+ * Get Js Component
+ *
* @param string $dataType
+ *
* @return string
*/
protected function getJsComponent($dataType)
@@ -91,14 +100,15 @@ protected function getJsComponent($dataType)
}
/**
+ * Get Data Type
+ *
* @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute
+ *
* @return string
*/
protected function getDataType($attribute)
{
- return isset($this->dataTypeMap[$attribute->getFrontendInput()])
- ? $this->dataTypeMap[$attribute->getFrontendInput()]
- : $this->dataTypeMap['default'];
+ return $this->dataTypeMap[$attribute->getFrontendInput()] ?? $this->dataTypeMap['default'];
}
/**
@@ -111,6 +121,6 @@ protected function getFilterType($frontendInput)
{
$filtersMap = ['date' => 'dateRange'];
$result = array_replace_recursive($this->dataTypeMap, $filtersMap);
- return isset($result[$frontendInput]) ? $result[$frontendInput] : $result['default'];
+ return $result[$frontendInput] ?? $result['default'];
}
}
diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php
index d4dc9ddd7ca3..09c9782fc0e3 100644
--- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php
+++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php
@@ -9,6 +9,8 @@
use Magento\Framework\View\Element\UiComponent\ContextInterface;
/**
+ * Class Thumbnail
+ *
* @api
* @since 100.0.2
*/
@@ -67,6 +69,8 @@ public function prepareDataSource(array $dataSource)
}
/**
+ * Get Alt
+ *
* @param array $row
*
* @return null|string
@@ -74,6 +78,6 @@ public function prepareDataSource(array $dataSource)
protected function getAlt($row)
{
$altField = $this->getData('config/altField') ?: self::ALT_FIELD;
- return isset($row[$altField]) ? $row[$altField] : null;
+ return $row[$altField] ?? null;
}
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
index d84f496e8191..7379600011bc 100755
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
@@ -11,6 +11,7 @@
use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Type as ProductType;
use Magento\Catalog\Model\ResourceModel\Eav\Attribute as EavAttribute;
use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory as EavAttributeFactory;
use Magento\Catalog\Ui\DataProvider\CatalogEavValidationRules;
@@ -419,7 +420,7 @@ public function modifyData(array $data)
foreach ($attributes as $attribute) {
if (null !== ($attributeValue = $this->setupAttributeData($attribute))) {
- if ($attribute->getFrontendInput() === 'price' && is_scalar($attributeValue)) {
+ if ($this->isPriceAttribute($attribute, $attributeValue)) {
$attributeValue = $this->formatPrice($attributeValue);
}
$data[$productId][self::DATA_SOURCE_DEFAULT][$attribute->getAttributeCode()] = $attributeValue;
@@ -430,6 +431,32 @@ public function modifyData(array $data)
return $data;
}
+ /**
+ * Obtain if given attribute is a price
+ *
+ * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute
+ * @param string|integer $attributeValue
+ * @return bool
+ */
+ private function isPriceAttribute(ProductAttributeInterface $attribute, $attributeValue)
+ {
+ return $attribute->getFrontendInput() === 'price'
+ && is_scalar($attributeValue)
+ && !$this->isBundleSpecialPrice($attribute);
+ }
+
+ /**
+ * Obtain if current product is bundle and given attribute is special_price
+ *
+ * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute
+ * @return bool
+ */
+ private function isBundleSpecialPrice(ProductAttributeInterface $attribute)
+ {
+ return $this->locator->getProduct()->getTypeId() === ProductType::TYPE_BUNDLE
+ && $attribute->getAttributeCode() === ProductAttributeInterface::CODE_SPECIAL_PRICE;
+ }
+
/**
* Resolve data persistence
*
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php
index 216bc16968fc..524927ac1c4b 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php
@@ -17,12 +17,14 @@
use Magento\Framework\View\DesignInterface;
use Magento\Store\Model\StoreManager;
use Magento\Store\Model\StoreManagerInterface;
+use Magento\Framework\View\DesignLoader;
/**
* Collect enough information about image rendering on front
* If you want to add new image, that should render on front you need
* to configure this class in di.xml
*
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Image implements ProductRenderCollectorInterface
{
@@ -51,6 +53,7 @@ class Image implements ProductRenderCollectorInterface
/**
* @var DesignInterface
+ * @deprecated 2.3.0 DesignLoader is used for design theme loading
*/
private $design;
@@ -59,6 +62,11 @@ class Image implements ProductRenderCollectorInterface
*/
private $imageRenderInfoFactory;
+ /**
+ * @var DesignLoader
+ */
+ private $designLoader;
+
/**
* Image constructor.
* @param ImageFactory $imageFactory
@@ -67,6 +75,7 @@ class Image implements ProductRenderCollectorInterface
* @param DesignInterface $design
* @param ImageInterfaceFactory $imageRenderInfoFactory
* @param array $imageCodes
+ * @param DesignLoader $designLoader
*/
public function __construct(
ImageFactory $imageFactory,
@@ -74,7 +83,8 @@ public function __construct(
StoreManagerInterface $storeManager,
DesignInterface $design,
ImageInterfaceFactory $imageRenderInfoFactory,
- array $imageCodes = []
+ array $imageCodes = [],
+ DesignLoader $designLoader = null
) {
$this->imageFactory = $imageFactory;
$this->imageCodes = $imageCodes;
@@ -82,6 +92,8 @@ public function __construct(
$this->storeManager = $storeManager;
$this->design = $design;
$this->imageRenderInfoFactory = $imageRenderInfoFactory;
+ $this->designLoader = $designLoader ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(DesignLoader::class);
}
/**
@@ -124,6 +136,8 @@ public function collect(ProductInterface $product, ProductRenderInterface $produ
}
/**
+ * Callback for emulating image creation
+ *
* Callback in which we emulate initialize default design theme, depends on current store, be settings store id
* from render info
*
@@ -136,7 +150,7 @@ public function collect(ProductInterface $product, ProductRenderInterface $produ
public function emulateImageCreating(ProductInterface $product, $imageCode, $storeId, ImageInterface $image)
{
$this->storeManager->setCurrentStore($storeId);
- $this->design->setDefaultDesignTheme();
+ $this->designLoader->load();
$imageHelper = $this->imageFactory->create();
$imageHelper->init($product, $imageCode);
diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml
index a802496d299b..7d2c3699ee2c 100644
--- a/app/code/Magento/Catalog/etc/di.xml
+++ b/app/code/Magento/Catalog/etc/di.xml
@@ -602,6 +602,13 @@
+
+
+
+ - Magento\Catalog\Model\ProductOptionProcessor
+
+
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
index 9865589556e7..93666470b1b2 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
@@ -248,7 +248,7 @@
try {
response = JSON.parse(transport.responseText);
} catch (e) {
- console.warn('An error occured while parsing response');
+ console.warn('An error occurred while parsing response');
}
if (!response || !response['parameters']) {
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml
index 07a801f42a78..195ac9242271 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml
@@ -334,7 +334,7 @@ if ($('is_required')) {
jQuery(function($) {
bindAttributeInputType();
- // @todo: refactor collapsable component
+ // @todo: refactor collapsible component
$('.attribute-popup .collapse, [data-role="advanced_fieldset-content"]')
.collapsable()
.collapse('hide');
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
index 54b945b48c10..670a943da0aa 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
@@ -188,6 +188,15 @@
for( j in config[i].children ) {
if(config[i].children[j].id) {
newNode = new Ext.tree.TreeNode(config[i].children[j]);
+
+ if (typeof newNode.ui.onTextChange === 'function') {
+ newNode.ui.onTextChange = function (_3, _4, _5) {
+ if (this.rendered) {
+ this.textNode.innerText = _4;
+ }
+ }
+ }
+ }
node.appendChild(newNode);
newNode.addListener('click', editSet.unregister);
}
@@ -195,13 +204,20 @@
}
}
}
- }
- editSet = function() {
- return {
- register : function(node) {
- editSet.currentNode = node;
- },
+
+ editSet = function () {
+ return {
+ register: function (node) {
+ editSet.currentNode = node;
+ if (typeof node.ui.onTextChange === 'function') {
+ node.ui.onTextChange = function (_3, _4, _5) {
+ if (this.rendered) {
+ this.textNode.innerText = _4;
+ }
+ }
+ }
+ },
unregister : function() {
editSet.currentNode = false;
@@ -293,6 +309,14 @@
allowDrag : true
});
+ if (typeof newNode.ui.onTextChange === 'function') {
+ newNode.ui.onTextChange = function (_3, _4, _5) {
+ if (this.rendered) {
+ this.textNode.innerText = _4;
+ }
+ }
+ }
+
TreePanels.root.appendChild(newNode);
newNode.addListener('beforemove', editSet.groupBeforeMove);
newNode.addListener('beforeinsert', editSet.groupBeforeInsert);
diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml
index 1e6082392977..cb0beb67c271 100644
--- a/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml
@@ -18,7 +18,7 @@
2
Base
-
+
Allowed file types: jpeg, gif, png.
Image
@@ -78,7 +78,7 @@
2
Thumbnail
-
+
Image
imageUploader
@@ -137,7 +137,7 @@
2
Small
-
+
Image
imageUploader
diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js
index 75ee3019cf4b..41f7a874c26f 100644
--- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js
+++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/edit.js
@@ -82,7 +82,7 @@ define([
return function (config, element) {
config = config || {};
jQuery(element).on('click', function () {
- categorySubmit(config.url, config.ajax);
+ categorySubmit();
});
};
});
diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js
index 6903a17bcdcc..a2804a8723ce 100644
--- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js
+++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js
@@ -469,26 +469,6 @@ define([
}
},
- /**
- * toggles Selects states (for IE) except those to be shown in popup
- */
- /*_toggleSelectsExceptBlock: function(flag) {
- if(Prototype.Browser.IE){
- if (this.blockForm) {
- var states = new Array;
- var selects = this.blockForm.getElementsByTagName("select");
- for(var i=0; i
isSaleable()): ?>
- getTypeInstance()->hasRequiredOptions($_product)): ?>
+ getTypeInstance()->isPossibleBuyFromList($_product)): ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml
index 11bbfea1ac8e..93542c4c9095 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml
@@ -66,7 +66,7 @@ if ($exist = ($block->getProductCollection() && $block->getProductCollection()->
isSaleable()): ?>
- getTypeInstance()->hasRequiredOptions($_item)): ?>
+ getTypeInstance()->isPossibleBuyFromList($_item)): ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml
index 615cd13fb6d3..ad75a3a6f074 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml
@@ -65,7 +65,7 @@ if ($exist = ($block->getProductCollection() && $block->getProductCollection()->
isSaleable()): ?>
- getTypeInstance()->hasRequiredOptions($_item)): ?>
+ getTypeInstance()->isPossibleBuyFromList($_item)): ?>
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 9080dab0a5c1..9298939791e4 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -132,6 +132,16 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
*/
const COL_NAME = 'name';
+ /**
+ * Column new_from_date.
+ */
+ const COL_NEW_FROM_DATE = 'new_from_date';
+
+ /**
+ * Column new_to_date.
+ */
+ const COL_NEW_TO_DATE = 'new_to_date';
+
/**
* Column product website.
*/
@@ -298,6 +308,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
ValidatorInterface::ERROR_INVALID_WEIGHT => 'Product weight is invalid',
ValidatorInterface::ERROR_DUPLICATE_URL_KEY => 'Url key: \'%s\' was already generated for an item with the SKU: \'%s\'. You need to specify the unique URL key manually',
ValidatorInterface::ERROR_DUPLICATE_MULTISELECT_VALUES => "Value for multiselect attribute %s contains duplicated values",
+ ValidatorInterface::ERROR_NEW_TO_DATE => 'Make sure new_to_date is later than or the same as new_from_date',
];
//@codingStandardsIgnoreEnd
@@ -319,8 +330,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
Product::COL_TYPE => 'product_type',
Product::COL_PRODUCT_WEBSITES => 'product_websites',
'status' => 'product_online',
- 'news_from_date' => 'new_from_date',
- 'news_to_date' => 'new_to_date',
+ 'news_from_date' => self::COL_NEW_FROM_DATE,
+ 'news_to_date' => self::COL_NEW_TO_DATE,
'options_container' => 'display_product_options_in',
'minimal_price' => 'map_price',
'msrp' => 'msrp_price',
@@ -1423,7 +1434,7 @@ protected function _saveProductCategories(array $categoriesData)
$delProductId[] = $productId;
foreach (array_keys($categories) as $categoryId) {
- $categoriesIn[] = ['product_id' => $productId, 'category_id' => $categoryId, 'position' => 1];
+ $categoriesIn[] = ['product_id' => $productId, 'category_id' => $categoryId, 'position' => 0];
}
}
if (Import::BEHAVIOR_APPEND != $this->getBehavior()) {
@@ -1794,6 +1805,7 @@ protected function _saveProducts()
if ($uploadedFile) {
$uploadedImages[$columnImage] = $uploadedFile;
} else {
+ unset($rowData[$column]);
$this->addRowError(
ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE,
$rowNum,
@@ -2549,6 +2561,20 @@ public function validateRow(array $rowData, $rowNum)
}
}
}
+
+ if (!empty($rowData[self::COL_NEW_FROM_DATE]) && !empty($rowData[self::COL_NEW_TO_DATE])
+ ) {
+ $newFromTimestamp = strtotime($this->dateTime->formatDate($rowData[self::COL_NEW_FROM_DATE], false));
+ $newToTimestamp = strtotime($this->dateTime->formatDate($rowData[self::COL_NEW_TO_DATE], false));
+ if ($newFromTimestamp > $newToTimestamp) {
+ $this->addRowError(
+ ValidatorInterface::ERROR_NEW_TO_DATE,
+ $rowNum,
+ $rowData[self::COL_NEW_TO_DATE]
+ );
+ }
+ }
+
return !$this->getErrorAggregator()->isRowInvalid($rowNum);
}
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php
index f41596ad185a..cbdc5f5beaaf 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php
@@ -87,6 +87,8 @@ interface RowValidatorInterface extends \Magento\Framework\Validator\ValidatorIn
const ERROR_DUPLICATE_MULTISELECT_VALUES = 'duplicatedMultiselectValues';
+ const ERROR_NEW_TO_DATE = 'invalidNewToDateValue';
+
/**
* Value that means all entities (e.g. websites, groups etc.)
*/
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php
index ae4be4a1e62b..7e6ada724a81 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php
@@ -101,7 +101,7 @@ class Uploader extends \Magento\MediaStorage\Model\File\Uploader
* @param \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension $validator
* @param \Magento\Framework\Filesystem $filesystem
* @param \Magento\Framework\Filesystem\File\ReadFactory $readFactory
- * @param null|string $filePath
+ * @param string|null $filePath
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function __construct(
@@ -146,20 +146,24 @@ public function init()
* @param string $fileName
* @param bool $renameFileOff
* @return array
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function move($fileName, $renameFileOff = false)
{
if ($renameFileOff) {
$this->setAllowRenameFiles(false);
}
+
+ if ($this->getTmpDir()) {
+ $filePath = $this->getTmpDir() . '/';
+ } else {
+ $filePath = '';
+ }
+
if (preg_match('/\bhttps?:\/\//i', $fileName, $matches)) {
$url = str_replace($matches[0], '', $fileName);
-
- if ($matches[0] === $this->httpScheme) {
- $read = $this->_readFactory->create($url, DriverPool::HTTP);
- } else {
- $read = $this->_readFactory->create($url, DriverPool::HTTPS);
- }
+ $driver = $matches[0] === $this->httpScheme ? DriverPool::HTTP : DriverPool::HTTPS;
+ $read = $this->_readFactory->create($url, $driver);
//only use filename (for URI with query parameters)
$parsedUrlPath = parse_url($url, PHP_URL_PATH);
@@ -170,11 +174,11 @@ public function move($fileName, $renameFileOff = false)
}
}
- if ($this->getTmpDir()) {
- $filePath = $this->getTmpDir() . '/';
- } else {
- $filePath = '';
+ $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
+ if ($fileExtension && !$this->checkAllowedExtension($fileExtension)) {
+ throw new \Magento\Framework\Exception\LocalizedException(__('Disallowed file type.'));
}
+
$fileName = preg_replace('/[^a-z0-9\._-]+/i', '', $fileName);
$filePath = $this->_directory->getRelativePath($filePath . $fileName);
$this->_directory->writeFile(
@@ -183,11 +187,6 @@ public function move($fileName, $renameFileOff = false)
);
}
- if ($this->getTmpDir()) {
- $filePath = $this->getTmpDir() . '/';
- } else {
- $filePath = '';
- }
$filePath = $this->_directory->getRelativePath($filePath . $fileName);
$this->_setUploadFile($filePath);
$destDir = $this->_directory->getAbsolutePath($this->getDestDir());
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php
index 262593377aa2..f734596de014 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php
@@ -93,14 +93,14 @@ protected function setUp()
$this->filesystem,
$this->readFactory,
])
- ->setMethods(['_setUploadFile', 'save', 'getTmpDir'])
+ ->setMethods(['_setUploadFile', 'save', 'getTmpDir', 'checkAllowedExtension'])
->getMock();
}
/**
* @dataProvider moveFileUrlDataProvider
*/
- public function testMoveFileUrl($fileUrl, $expectedHost, $expectedFileName)
+ public function testMoveFileUrl($fileUrl, $expectedHost, $expectedFileName, $checkAllowedExtension)
{
$destDir = 'var/dest/dir';
$expectedRelativeFilePath = $expectedFileName;
@@ -128,6 +128,9 @@ public function testMoveFileUrl($fileUrl, $expectedHost, $expectedFileName)
$this->uploader->expects($this->once())->method('_setUploadFile')->will($this->returnSelf());
$this->uploader->expects($this->once())->method('save')->with($destDir . '/' . $expectedFileName)
->willReturn(['name' => $expectedFileName, 'path' => 'absPath']);
+ $this->uploader->expects($this->exactly($checkAllowedExtension))
+ ->method('checkAllowedExtension')
+ ->willReturn(true);
$this->uploader->setDestDir($destDir);
$result = $this->uploader->move($fileUrl);
@@ -224,31 +227,37 @@ public function moveFileUrlDataProvider()
'$fileUrl' => 'http://test_uploader_file',
'$expectedHost' => 'test_uploader_file',
'$expectedFileName' => 'test_uploader_file',
+ '$checkAllowedExtension' => 0
],
[
'$fileUrl' => 'https://!:^&`;file',
'$expectedHost' => '!:^&`;file',
'$expectedFileName' => 'file',
+ '$checkAllowedExtension' => 0
],
[
'$fileUrl' => 'https://www.google.com/image.jpg',
'$expectedHost' => 'www.google.com/image.jpg',
'$expectedFileName' => 'image.jpg',
+ '$checkAllowedExtension' => 1
],
[
'$fileUrl' => 'https://www.google.com/image.jpg?param=1',
'$expectedHost' => 'www.google.com/image.jpg?param=1',
'$expectedFileName' => 'image.jpg',
+ '$checkAllowedExtension' => 1
],
[
'$fileUrl' => 'https://www.google.com/image.jpg?param=1¶m=2',
'$expectedHost' => 'www.google.com/image.jpg?param=1¶m=2',
'$expectedFileName' => 'image.jpg',
+ '$checkAllowedExtension' => 1
],
[
'$fileUrl' => 'http://www.google.com/image.jpg?param=1¶m=2',
'$expectedHost' => 'www.google.com/image.jpg?param=1¶m=2',
'$expectedFileName' => 'image.jpg',
+ '$checkAllowedExtension' => 1
],
];
}
diff --git a/app/code/Magento/CatalogInventory/Block/Stockqty/AbstractStockqty.php b/app/code/Magento/CatalogInventory/Block/Stockqty/AbstractStockqty.php
index 568fa600ec52..4c8f356519e2 100644
--- a/app/code/Magento/CatalogInventory/Block/Stockqty/AbstractStockqty.php
+++ b/app/code/Magento/CatalogInventory/Block/Stockqty/AbstractStockqty.php
@@ -131,7 +131,9 @@ public function getPlaceholderId()
*/
public function isMsgVisible()
{
- return $this->getStockQty() > 0 && $this->getStockQtyLeft() <= $this->getThresholdQty();
+ return $this->getStockQty() > 0
+ && $this->getStockQtyLeft() > 0
+ && $this->getStockQtyLeft() <= $this->getThresholdQty();
}
/**
diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php b/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php
index 5e7210c1b744..f9a49d4f8d12 100644
--- a/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php
+++ b/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php
@@ -9,11 +9,11 @@
use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\CatalogInventory\Model\ResourceModel\Stock\Item;
-use Magento\CatalogInventory\Model\Stock;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceModifierInterface;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructure;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\DB\Query\Generator;
/**
* Class for filter product price index.
@@ -40,22 +40,38 @@ class ProductPriceIndexFilter implements PriceModifierInterface
*/
private $connectionName;
+ /**
+ * @var Generator
+ */
+ private $batchQueryGenerator;
+
+ /**
+ * @var int
+ */
+ private $batchSize;
+
/**
* @param StockConfigurationInterface $stockConfiguration
* @param Item $stockItem
* @param ResourceConnection $resourceConnection
* @param string $connectionName
+ * @param Generator $batchQueryGenerator
+ * @param int $batchSize
*/
public function __construct(
StockConfigurationInterface $stockConfiguration,
Item $stockItem,
ResourceConnection $resourceConnection = null,
- $connectionName = 'indexer'
+ $connectionName = 'indexer',
+ Generator $batchQueryGenerator = null,
+ $batchSize = 100
) {
$this->stockConfiguration = $stockConfiguration;
$this->stockItem = $stockItem;
$this->resourceConnection = $resourceConnection ?: ObjectManager::getInstance()->get(ResourceConnection::class);
$this->connectionName = $connectionName;
+ $this->batchQueryGenerator = $batchQueryGenerator ?: ObjectManager::getInstance()->get(Generator::class);
+ $this->batchSize = $batchSize;
}
/**
@@ -76,32 +92,37 @@ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds =
$connection = $this->resourceConnection->getConnection($this->connectionName);
$select = $connection->select();
+
$select->from(
- ['price_index' => $priceTable->getTableName()],
- []
- );
- $select->joinInner(
['stock_item' => $this->stockItem->getMainTable()],
- 'stock_item.product_id = price_index.' . $priceTable->getEntityField()
- . ' AND stock_item.stock_id = ' . Stock::DEFAULT_STOCK_ID,
- []
+ ['stock_item.product_id', 'MAX(stock_item.is_in_stock) as max_is_in_stock']
);
+
if ($this->stockConfiguration->getManageStock()) {
- $stockStatus = $connection->getCheckSql(
- 'use_config_manage_stock = 0 AND manage_stock = 0',
- Stock::STOCK_IN_STOCK,
- 'is_in_stock'
- );
+ $select->where('stock_item.use_config_manage_stock = 1 OR stock_item.manage_stock = 1');
} else {
- $stockStatus = $connection->getCheckSql(
- 'use_config_manage_stock = 0 AND manage_stock = 1',
- 'is_in_stock',
- Stock::STOCK_IN_STOCK
- );
+ $select->where('stock_item.use_config_manage_stock = 0 AND stock_item.manage_stock = 1');
}
- $select->where($stockStatus . ' = ?', Stock::STOCK_OUT_OF_STOCK);
- $query = $select->deleteFromSelect('price_index');
- $connection->query($query);
+ $select->group('stock_item.product_id');
+ $select->having('max_is_in_stock = 0');
+
+ $batchSelectIterator = $this->batchQueryGenerator->generate(
+ 'product_id',
+ $select,
+ $this->batchSize,
+ \Magento\Framework\DB\Query\BatchIteratorInterface::UNIQUE_FIELD_ITERATOR
+ );
+
+ foreach ($batchSelectIterator as $select) {
+ $productIds = null;
+ foreach ($connection->query($select)->fetchAll() as $row) {
+ $productIds[] = $row['product_id'];
+ }
+ if ($productIds !== null) {
+ $where = [$priceTable->getEntityField() .' IN (?)' => $productIds];
+ $connection->delete($priceTable->getTableName(), $where);
+ }
+ }
}
}
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml
index 534541c96ea8..27e8d1e99990 100644
--- a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml
@@ -112,6 +112,9 @@
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml
index 716a363ec5d7..875a7842f21f 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml
index 072385c46645..10db68e9053d 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml
@@ -18,7 +18,7 @@
-
+
@@ -78,7 +78,7 @@
-
+
@@ -104,7 +104,7 @@
-
+
@@ -130,7 +130,7 @@
-
+
@@ -156,7 +156,7 @@
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml
index 83770ffff5ea..06f3682aedd8 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml
@@ -18,7 +18,7 @@
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml
index 2e09ee713473..cac7c94de446 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml
@@ -17,6 +17,9 @@
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml
index 55f775e40fea..c6ecc1c6d965 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml
@@ -18,7 +18,7 @@
-
+
diff --git a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php
index 17d70e38d3b6..184fd9cfd5b3 100644
--- a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php
+++ b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php
@@ -17,6 +17,11 @@
*/
class Result extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface, HttpPostActionInterface
{
+ /**
+ * No results default handle.
+ */
+ const DEFAULT_NO_RESULT_HANDLE = 'catalogsearch_advanced_result_noresults';
+
/**
* Url factory
*
@@ -55,7 +60,16 @@ public function execute()
{
try {
$this->_catalogSearchAdvanced->addFilters($this->getRequest()->getQueryValue());
- $this->_view->loadLayout();
+ $size = $this->_catalogSearchAdvanced->getProductCollection()->getSize();
+
+ $handles = null;
+ if ($size == 0) {
+ $this->_view->getPage()->initLayout();
+ $handles = $this->_view->getLayout()->getUpdate()->getHandles();
+ $handles[] = static::DEFAULT_NO_RESULT_HANDLE;
+ }
+
+ $this->_view->loadLayout($handles);
$this->_view->renderLayout();
} catch (\Magento\Framework\Exception\LocalizedException $e) {
$this->messageManager->addError($e->getMessage());
diff --git a/app/code/Magento/CatalogSearch/Controller/Result/Index.php b/app/code/Magento/CatalogSearch/Controller/Result/Index.php
index fba4572cf2c1..975c6ba1e7eb 100644
--- a/app/code/Magento/CatalogSearch/Controller/Result/Index.php
+++ b/app/code/Magento/CatalogSearch/Controller/Result/Index.php
@@ -20,6 +20,11 @@
*/
class Index extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface, HttpPostActionInterface
{
+ /**
+ * No results default handle.
+ */
+ const DEFAULT_NO_RESULT_HANDLE = 'catalogsearch_result_index_noresults';
+
/**
* Catalog session
*
@@ -90,12 +95,19 @@ public function execute()
$getAdditionalRequestParameters = $this->getRequest()->getParams();
unset($getAdditionalRequestParameters[QueryFactory::QUERY_VAR_NAME]);
+ $handles = null;
+ if ($query->getNumResults() == 0) {
+ $this->_view->getPage()->initLayout();
+ $handles = $this->_view->getLayout()->getUpdate()->getHandles();
+ $handles[] = static::DEFAULT_NO_RESULT_HANDLE;
+ }
+
if (empty($getAdditionalRequestParameters) &&
$this->_objectManager->get(PopularSearchTerms::class)->isCacheable($queryText, $storeId)
) {
- $this->getCacheableResult($catalogSearchHelper, $query);
+ $this->getCacheableResult($catalogSearchHelper, $query, $handles);
} else {
- $this->getNotCacheableResult($catalogSearchHelper, $query);
+ $this->getNotCacheableResult($catalogSearchHelper, $query, $handles);
}
} else {
$this->getResponse()->setRedirect($this->_redirect->getRedirectUrl());
@@ -107,9 +119,10 @@ public function execute()
*
* @param \Magento\CatalogSearch\Helper\Data $catalogSearchHelper
* @param \Magento\Search\Model\Query $query
+ * @param array $handles
* @return void
*/
- private function getCacheableResult($catalogSearchHelper, $query)
+ private function getCacheableResult($catalogSearchHelper, $query, $handles)
{
if (!$catalogSearchHelper->isMinQueryLength()) {
$redirect = $query->getRedirect();
@@ -121,7 +134,7 @@ private function getCacheableResult($catalogSearchHelper, $query)
$catalogSearchHelper->checkNotes();
- $this->_view->loadLayout();
+ $this->_view->loadLayout($handles);
$this->_view->renderLayout();
}
@@ -130,11 +143,12 @@ private function getCacheableResult($catalogSearchHelper, $query)
*
* @param \Magento\CatalogSearch\Helper\Data $catalogSearchHelper
* @param \Magento\Search\Model\Query $query
+ * @param array $handles
* @return void
*
* @throws \Magento\Framework\Exception\LocalizedException
*/
- private function getNotCacheableResult($catalogSearchHelper, $query)
+ private function getNotCacheableResult($catalogSearchHelper, $query, $handles)
{
if ($catalogSearchHelper->isMinQueryLength()) {
$query->setId(0)->setIsActive(1)->setIsProcessed(1);
@@ -149,7 +163,7 @@ private function getNotCacheableResult($catalogSearchHelper, $query)
$catalogSearchHelper->checkNotes();
- $this->_view->loadLayout();
+ $this->_view->loadLayout($handles);
$this->getResponse()->setNoCacheHeaders();
$this->_view->renderLayout();
}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/QueryChecker/FullTextSearchCheck.php b/app/code/Magento/CatalogSearch/Model/Search/QueryChecker/FullTextSearchCheck.php
index c122bae15cb0..a47ea5437520 100644
--- a/app/code/Magento/CatalogSearch/Model/Search/QueryChecker/FullTextSearchCheck.php
+++ b/app/code/Magento/CatalogSearch/Model/Search/QueryChecker/FullTextSearchCheck.php
@@ -25,7 +25,7 @@ class FullTextSearchCheck
* to join catalog_eav_attribute table to search query or not
*
* In case when the $query does not requires full text search
- * - we can skipp joining catalog_eav_attribute table because it becomes excessive
+ * - we can skip joining catalog_eav_attribute table because it becomes excessive
*
* @param QueryInterface $query
* @return bool
@@ -37,6 +37,8 @@ public function isRequiredForQuery(QueryInterface $query)
}
/**
+ * Process query
+ *
* @param QueryInterface $query
* @return bool
* @throws \InvalidArgumentException
@@ -62,6 +64,8 @@ private function processQuery(QueryInterface $query)
}
/**
+ * Process boolean query
+ *
* @param BoolExpression $query
* @return bool
* @throws \InvalidArgumentException
@@ -90,6 +94,8 @@ private function processBoolQuery(BoolExpression $query)
}
/**
+ * Process filter query
+ *
* @param Filter $query
* @return bool
* @throws \InvalidArgumentException
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
index 7ca86b8b14a4..387a7547f4da 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
@@ -15,6 +15,7 @@
+
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php
index 891f008979e1..2faacea24262 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php
@@ -10,6 +10,11 @@
*/
class ResultTest extends \PHPUnit\Framework\TestCase
{
+ /**
+ * Test result action filters set before load layout scenario
+ *
+ * @return void
+ */
public function testResultActionFiltersSetBeforeLoadLayout()
{
$filters = null;
@@ -27,9 +32,15 @@ function () use (&$filters, $expectedQuery) {
$request = $this->createPartialMock(\Magento\Framework\App\Console\Request::class, ['getQueryValue']);
$request->expects($this->once())->method('getQueryValue')->will($this->returnValue($expectedQuery));
+ $collection = $this->createPartialMock(
+ \Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection::class,
+ ['getSize']
+ );
+ $collection->expects($this->once())->method('getSize')->will($this->returnValue(1));
+
$catalogSearchAdvanced = $this->createPartialMock(
\Magento\CatalogSearch\Model\Advanced::class,
- ['addFilters', '__wakeup']
+ ['addFilters', '__wakeup', 'getProductCollection']
);
$catalogSearchAdvanced->expects($this->once())->method('addFilters')->will(
$this->returnCallback(
@@ -38,6 +49,8 @@ function ($added) use (&$filters) {
}
)
);
+ $catalogSearchAdvanced->expects($this->once())->method('getProductCollection')
+ ->will($this->returnValue($collection));
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$context = $objectManager->getObject(
@@ -53,6 +66,11 @@ function ($added) use (&$filters) {
$instance->execute();
}
+ /**
+ * Test url set on exception scenario
+ *
+ * @return void
+ */
public function testUrlSetOnException()
{
$redirectResultMock = $this->createMock(\Magento\Framework\Controller\Result\Redirect::class);
@@ -131,11 +149,71 @@ public function testUrlSetOnException()
/** @var \Magento\CatalogSearch\Controller\Advanced\Result $instance */
$instance = $objectManager->getObject(
\Magento\CatalogSearch\Controller\Advanced\Result::class,
- ['context' => $contextMock,
- 'catalogSearchAdvanced' => $catalogSearchAdvanced,
- 'urlFactory' => $urlFactoryMock
+ [
+ 'context' => $contextMock,
+ 'catalogSearchAdvanced' => $catalogSearchAdvanced,
+ 'urlFactory' => $urlFactoryMock
]
);
$this->assertEquals($redirectResultMock, $instance->execute());
}
+
+ /**
+ * Test no result handle scenario
+ *
+ * @return void
+ */
+ public function testNoResultsHandle()
+ {
+ $expectedQuery = 'notExistTerm';
+
+ $update = $this->createPartialMock(\Magento\Framework\View\Model\Layout\Merge::class, ['getHandles']);
+ $update->expects($this->once())->method('getHandles')->will($this->returnValue([]));
+
+ $layout = $this->createPartialMock(\Magento\Framework\View\Result\Layout::class, ['getUpdate']);
+ $layout->expects($this->once())->method('getUpdate')->will($this->returnValue($update));
+
+ $page = $this->createPartialMock(\Magento\Framework\View\Result\Page::class, ['initLayout']);
+
+ $view = $this->createPartialMock(
+ \Magento\Framework\App\View::class,
+ ['loadLayout', 'renderLayout', 'getPage', 'getLayout']
+ );
+
+ $view->expects($this->once())->method('loadLayout')
+ ->with([\Magento\CatalogSearch\Controller\Advanced\Result::DEFAULT_NO_RESULT_HANDLE]);
+
+ $view->expects($this->once())->method('getPage')->will($this->returnValue($page));
+ $view->expects($this->once())->method('getLayout')->will($this->returnValue($layout));
+
+ $request = $this->createPartialMock(\Magento\Framework\App\Console\Request::class, ['getQueryValue']);
+ $request->expects($this->once())->method('getQueryValue')->will($this->returnValue($expectedQuery));
+
+ $collection = $this->createPartialMock(
+ \Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection::class,
+ ['getSize']
+ );
+ $collection->expects($this->once())->method('getSize')->will($this->returnValue(0));
+
+ $catalogSearchAdvanced = $this->createPartialMock(
+ \Magento\CatalogSearch\Model\Advanced::class,
+ ['addFilters', '__wakeup', 'getProductCollection']
+ );
+
+ $catalogSearchAdvanced->expects($this->once())->method('getProductCollection')
+ ->will($this->returnValue($collection));
+
+ $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+ $context = $objectManager->getObject(
+ \Magento\Framework\App\Action\Context::class,
+ ['view' => $view, 'request' => $request]
+ );
+
+ /** @var \Magento\CatalogSearch\Controller\Advanced\Result $instance */
+ $instance = $objectManager->getObject(
+ \Magento\CatalogSearch\Controller\Advanced\Result::class,
+ ['context' => $context, 'catalogSearchAdvanced' => $catalogSearchAdvanced]
+ );
+ $instance->execute();
+ }
}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Move.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Move.php
index 17d12ba563eb..f3984bf7d62a 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Move.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Move.php
@@ -6,9 +6,14 @@
namespace Magento\CatalogUrlRewrite\Model\Category\Plugin\Category;
use Magento\Catalog\Model\Category;
+use Magento\Catalog\Model\CategoryFactory;
use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider;
+use Magento\Store\Model\Store;
+/**
+ * Perform url updating for children categories.
+ */
class Move
{
/**
@@ -21,16 +26,24 @@ class Move
*/
private $childrenCategoriesProvider;
+ /**
+ * @var CategoryFactory
+ */
+ private $categoryFactory;
+
/**
* @param CategoryUrlPathGenerator $categoryUrlPathGenerator
* @param ChildrenCategoriesProvider $childrenCategoriesProvider
+ * @param CategoryFactory $categoryFactory
*/
public function __construct(
CategoryUrlPathGenerator $categoryUrlPathGenerator,
- ChildrenCategoriesProvider $childrenCategoriesProvider
+ ChildrenCategoriesProvider $childrenCategoriesProvider,
+ CategoryFactory $categoryFactory
) {
$this->categoryUrlPathGenerator = $categoryUrlPathGenerator;
$this->childrenCategoriesProvider = $childrenCategoriesProvider;
+ $this->categoryFactory = $categoryFactory;
}
/**
@@ -51,20 +64,57 @@ public function afterChangeParent(
Category $newParent,
$afterCategoryId
) {
- $category->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($category));
- $category->getResource()->saveAttribute($category, 'url_path');
- $this->updateUrlPathForChildren($category);
+ $categoryStoreId = $category->getStoreId();
+ foreach ($category->getStoreIds() as $storeId) {
+ $category->setStoreId($storeId);
+ if (!$this->isGlobalScope($storeId)) {
+ $this->updateCategoryUrlKeyForStore($category);
+ $category->unsUrlPath();
+ $category->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($category));
+ $category->getResource()->saveAttribute($category, 'url_path');
+ $this->updateUrlPathForChildren($category);
+ }
+ }
+ $category->setStoreId($categoryStoreId);
return $result;
}
/**
+ * Set category url_key according to current category store id.
+ *
+ * @param Category $category
+ * @return void
+ */
+ private function updateCategoryUrlKeyForStore(Category $category)
+ {
+ $item = $this->categoryFactory->create();
+ $item->setStoreId($category->getStoreId());
+ $item->load($category->getId());
+ $category->setUrlKey($item->getUrlKey());
+ }
+
+ /**
+ * Check is global scope.
+ *
+ * @param int|null $storeId
+ * @return bool
+ */
+ private function isGlobalScope($storeId)
+ {
+ return null === $storeId || $storeId == Store::DEFAULT_STORE_ID;
+ }
+
+ /**
+ * Updates url_path for child categories.
+ *
* @param Category $category
* @return void
*/
- protected function updateUrlPathForChildren($category)
+ private function updateUrlPathForChildren($category)
{
foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) {
+ $childCategory->setStoreId($category->getStoreId());
$childCategory->unsUrlPath();
$childCategory->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($childCategory));
$childCategory->getResource()->saveAttribute($childCategory, 'url_path');
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlPathGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlPathGenerator.php
index ee20b0e934b5..cba9218ce7c7 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlPathGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlPathGenerator.php
@@ -8,6 +8,9 @@
use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Model\Category;
+/**
+ * Class for generation category url_path
+ */
class CategoryUrlPathGenerator
{
/**
@@ -61,9 +64,11 @@ public function __construct(
* Build category URL path
*
* @param \Magento\Catalog\Api\Data\CategoryInterface|\Magento\Framework\Model\AbstractModel $category
+ * @param null|\Magento\Catalog\Api\Data\CategoryInterface|\Magento\Framework\Model\AbstractModel $parentCategory
* @return string
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
- public function getUrlPath($category)
+ public function getUrlPath($category, $parentCategory = null)
{
if (in_array($category->getParentId(), [Category::ROOT_CATEGORY_ID, Category::TREE_ROOT_ID])) {
return '';
@@ -77,15 +82,17 @@ public function getUrlPath($category)
return $category->getUrlPath();
}
if ($this->isNeedToGenerateUrlPathForParent($category)) {
- $parentPath = $this->getUrlPath(
- $this->categoryRepository->get($category->getParentId(), $category->getStoreId())
- );
+ $parentCategory = $parentCategory === null ?
+ $this->categoryRepository->get($category->getParentId(), $category->getStoreId()) : $parentCategory;
+ $parentPath = $this->getUrlPath($parentCategory);
$path = $parentPath === '' ? $path : $parentPath . '/' . $path;
}
return $path;
}
/**
+ * Define whether we should generate URL path for parent
+ *
* @param \Magento\Catalog\Model\Category $category
* @return bool
*/
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php
index b2da0ab39f31..a86604672e2b 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php
@@ -15,6 +15,9 @@
use Magento\Framework\App\ObjectManager;
use Magento\UrlRewrite\Model\MergeDataProviderFactory;
+/**
+ * Generate list of urls.
+ */
class CategoryUrlRewriteGenerator
{
/** Entity type code */
@@ -84,6 +87,8 @@ public function __construct(
}
/**
+ * Generate list of urls.
+ *
* @param \Magento\Catalog\Model\Category $category
* @param bool $overrideStoreUrls
* @param int|null $rootCategoryId
@@ -119,6 +124,7 @@ protected function generateForGlobalScope(
$mergeDataProvider = clone $this->mergeDataProviderPrototype;
$categoryId = $category->getId();
foreach ($category->getStoreIds() as $storeId) {
+ $category->setStoreId($storeId);
if (!$this->isGlobalScope($storeId)
&& $this->isOverrideUrlsForStore($storeId, $categoryId, $overrideStoreUrls)
) {
@@ -131,6 +137,8 @@ protected function generateForGlobalScope(
}
/**
+ * Checks if urls should be overridden for store.
+ *
* @param int $storeId
* @param int $categoryId
* @param bool $overrideStoreUrls
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ObjectRegistry.php b/app/code/Magento/CatalogUrlRewrite/Model/ObjectRegistry.php
index 019959f9c1fe..a048c216139e 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/ObjectRegistry.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/ObjectRegistry.php
@@ -5,6 +5,9 @@
*/
namespace Magento\CatalogUrlRewrite\Model;
+/**
+ * Class ObjectRegistry
+ */
class ObjectRegistry
{
/**
@@ -26,15 +29,19 @@ public function __construct($entities)
}
/**
+ * Get Entity
+ *
* @param int $entityId
* @return \Magento\Framework\DataObject|null
*/
public function get($entityId)
{
- return isset($this->entitiesMap[$entityId]) ? $this->entitiesMap[$entityId] : null;
+ return $this->entitiesMap[$entityId] ?? null;
}
/**
+ * List Entities
+ *
* @return \Magento\Framework\DataObject[]
*/
public function getList()
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/WebapiProductUrlPathGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/WebapiProductUrlPathGenerator.php
deleted file mode 100644
index ae93b76b3157..000000000000
--- a/app/code/Magento/CatalogUrlRewrite/Model/WebapiProductUrlPathGenerator.php
+++ /dev/null
@@ -1,72 +0,0 @@
-collectionFactory = $collectionFactory;
- }
-
- /**
- * @inheritdoc
- */
- protected function prepareProductUrlKey(\Magento\Catalog\Model\Product $product)
- {
- $urlKey = $product->getUrlKey();
- if ($urlKey === '' || $urlKey === null) {
- $urlKey = $this->prepareUrlKey($product->formatUrlKey($product->getName()));
- }
- return $product->formatUrlKey($urlKey);
- }
-
- /**
- * Crete url key if it does not exist yet.
- *
- * @param string $urlKey
- * @return string
- */
- private function prepareUrlKey(string $urlKey) : string
- {
- /** @var ProductCollection $collection */
- $collection = $this->collectionFactory->create();
- $collection->addFieldToFilter('url_key', ['like' => $urlKey]);
- if ($collection->getSize() !== 0) {
- $urlKey = $urlKey . '-1';
- $urlKey = $this->prepareUrlKey($urlKey);
- }
-
- return $urlKey;
- }
-}
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php
index d692918aff6a..713dd6ac0c73 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php
@@ -71,16 +71,33 @@ public function execute(\Magento\Framework\Event\Observer $observer)
$useDefaultAttribute = !$category->isObjectNew() && !empty($category->getData('use_default')['url_key']);
if ($category->getUrlKey() !== false && !$useDefaultAttribute) {
$resultUrlKey = $this->categoryUrlPathGenerator->getUrlKey($category);
- if (empty($resultUrlKey)) {
- throw new \Magento\Framework\Exception\LocalizedException(__('Invalid URL key'));
- }
- $category->setUrlKey($resultUrlKey)
- ->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($category));
- if (!$category->isObjectNew()) {
- $category->getResource()->saveAttribute($category, 'url_path');
- if ($category->dataHasChangedFor('url_path')) {
- $this->updateUrlPathForChildren($category);
- }
+ $this->updateUrlKey($category, $resultUrlKey);
+ } else if ($useDefaultAttribute) {
+ $resultUrlKey = $category->formatUrlKey($category->getOrigData('name'));
+ $this->updateUrlKey($category, $resultUrlKey);
+ $category->setUrlKey(null)->setUrlPath(null);
+ }
+ }
+
+ /**
+ * Update Url Key
+ *
+ * @param Category $category
+ * @param string $urlKey
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ */
+ private function updateUrlKey($category, $urlKey)
+ {
+ if (empty($urlKey)) {
+ throw new \Magento\Framework\Exception\LocalizedException(__('Invalid URL key'));
+ }
+ $category->setUrlKey($urlKey)
+ ->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($category));
+ if (!$category->isObjectNew()) {
+ $category->getResource()->saveAttribute($category, 'url_path');
+ if ($category->dataHasChangedFor('url_path')) {
+ $this->updateUrlPathForChildren($category);
}
}
}
@@ -110,8 +127,13 @@ protected function updateUrlPathForChildren(Category $category)
} else {
$children = $this->childrenCategoriesProvider->getChildren($category, true);
foreach ($children as $child) {
+ /** @var Category $child */
$child->setStoreId($category->getStoreId());
- $this->updateUrlPathForCategory($child);
+ if ($child->getParentId() === $category->getId()) {
+ $this->updateUrlPathForCategory($child, $category);
+ } else {
+ $this->updateUrlPathForCategory($child);
+ }
}
}
}
@@ -131,12 +153,14 @@ protected function isGlobalScope($storeId)
* Update url path for category.
*
* @param Category $category
+ * @param Category|null $parentCategory
* @return void
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
- protected function updateUrlPathForCategory(Category $category)
+ protected function updateUrlPathForCategory(Category $category, Category $parentCategory = null)
{
$category->unsUrlPath();
- $category->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($category));
+ $category->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($category, $parentCategory));
$category->getResource()->saveAttribute($category, 'url_path');
}
}
diff --git a/app/code/Magento/CatalogUrlRewrite/Plugin/Webapi/Controller/Rest/InputParamsResolver.php b/app/code/Magento/CatalogUrlRewrite/Plugin/Webapi/Controller/Rest/InputParamsResolver.php
new file mode 100644
index 000000000000..4e8e3840693a
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Plugin/Webapi/Controller/Rest/InputParamsResolver.php
@@ -0,0 +1,88 @@
+request = $request;
+ }
+
+ /**
+ * Add 'save_rewrites_history' param to the product data
+ *
+ * @see \Magento\CatalogUrlRewrite\Plugin\Catalog\Controller\Adminhtml\Product\Initialization\Helper
+ * @param \Magento\Webapi\Controller\Rest\InputParamsResolver $subject
+ * @param array $result
+ * @return array
+ */
+ public function afterResolve(\Magento\Webapi\Controller\Rest\InputParamsResolver $subject, array $result): array
+ {
+ $route = $subject->getRoute();
+ $serviceMethodName = $route->getServiceMethod();
+ $serviceClassName = $route->getServiceClass();
+ $requestBodyParams = $this->request->getBodyParams();
+
+ if ($this->isProductSaveCalled($serviceClassName, $serviceMethodName)
+ && $this->isCustomAttributesExists($requestBodyParams)) {
+ foreach ($requestBodyParams['product']['custom_attributes'] as $attribute) {
+ if ($attribute['attribute_code'] === 'save_rewrites_history') {
+ foreach ($result as $resultItem) {
+ if ($resultItem instanceof \Magento\Catalog\Model\Product) {
+ $resultItem->setData('save_rewrites_history', (bool)$attribute['value']);
+ break 2;
+ }
+ }
+ break;
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Check that product save method called
+ *
+ * @param string $serviceClassName
+ * @param string $serviceMethodName
+ * @return bool
+ */
+ private function isProductSaveCalled(string $serviceClassName, string $serviceMethodName): bool
+ {
+ return $serviceClassName === ProductRepositoryInterface::class && $serviceMethodName === 'save';
+ }
+
+ /**
+ * Check is any custom options exists in product data
+ *
+ * @param array $requestBodyParams
+ * @return bool
+ */
+ private function isCustomAttributesExists(array $requestBodyParams): bool
+ {
+ return !empty($requestBodyParams['product']['custom_attributes']);
+ }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/MoveTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/MoveTest.php
index f91a55c11b97..85e883702715 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/MoveTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/Category/MoveTest.php
@@ -5,6 +5,7 @@
*/
namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Category\Plugin\Category;
+use Magento\Catalog\Model\CategoryFactory;
use Magento\CatalogUrlRewrite\Model\Category\Plugin\Category\Move as CategoryMovePlugin;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
@@ -39,6 +40,11 @@ class MoveTest extends \PHPUnit\Framework\TestCase
*/
private $categoryMock;
+ /**
+ * @var CategoryFactory|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $categoryFactory;
+
/**
* @var CategoryMovePlugin
*/
@@ -55,28 +61,44 @@ protected function setUp()
->disableOriginalConstructor()
->setMethods(['getChildren'])
->getMock();
+ $this->categoryFactory = $this->getMockBuilder(CategoryFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->subjectMock = $this->getMockBuilder(CategoryResourceModel::class)
->disableOriginalConstructor()
->getMock();
$this->categoryMock = $this->getMockBuilder(Category::class)
->disableOriginalConstructor()
- ->setMethods(['getResource', 'setUrlPath'])
+ ->setMethods(['getResource', 'setUrlPath', 'getStoreIds', 'getStoreId', 'setStoreId'])
->getMock();
$this->plugin = $this->objectManager->getObject(
CategoryMovePlugin::class,
[
'categoryUrlPathGenerator' => $this->categoryUrlPathGeneratorMock,
- 'childrenCategoriesProvider' => $this->childrenCategoriesProviderMock
+ 'childrenCategoriesProvider' => $this->childrenCategoriesProviderMock,
+ 'categoryFactory' => $this->categoryFactory
]
);
}
+ /**
+ * Tests url updating for children categories.
+ */
public function testAfterChangeParent()
{
$urlPath = 'test/path';
- $this->categoryMock->expects($this->once())
- ->method('getResource')
+ $storeIds = [1];
+ $originalCategory = $this->getMockBuilder(Category::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->categoryFactory->method('create')
+ ->willReturn($originalCategory);
+
+ $this->categoryMock->method('getResource')
->willReturn($this->subjectMock);
+ $this->categoryMock->expects($this->once())
+ ->method('getStoreIds')
+ ->willReturn($storeIds);
$this->childrenCategoriesProviderMock->expects($this->once())
->method('getChildren')
->with($this->categoryMock, true)
@@ -85,9 +107,6 @@ public function testAfterChangeParent()
->method('getUrlPath')
->with($this->categoryMock)
->willReturn($urlPath);
- $this->categoryMock->expects($this->once())
- ->method('getResource')
- ->willReturn($this->subjectMock);
$this->categoryMock->expects($this->once())
->method('setUrlPath')
->with($urlPath);
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Plugin/Webapi/Controller/Rest/InputParamsResolverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Plugin/Webapi/Controller/Rest/InputParamsResolverTest.php
new file mode 100644
index 000000000000..8e705b2c09f6
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Plugin/Webapi/Controller/Rest/InputParamsResolverTest.php
@@ -0,0 +1,116 @@
+saveRewritesHistory = 'save_rewrites_history';
+ $this->requestBodyParams = [
+ 'product' => [
+ 'sku' => 'test',
+ 'custom_attributes' => [
+ ['attribute_code' => $this->saveRewritesHistory, 'value' => 1]
+ ]
+ ]
+ ];
+
+ $this->route = $this->createPartialMock(Route::class, ['getServiceMethod', 'getServiceClass']);
+ $this->request = $this->createPartialMock(RestRequest::class, ['getBodyParams']);
+ $this->request->expects($this->any())->method('getBodyParams')->willReturn($this->requestBodyParams);
+ $this->subject = $this->createPartialMock(InputParamsResolver::class, ['getRoute']);
+ $this->subject->expects($this->any())->method('getRoute')->willReturn($this->route);
+ $this->product = $this->createPartialMock(Product::class, ['setData']);
+
+ $this->result = [false, $this->product, 'test'];
+
+ $this->objectManager = new ObjectManager($this);
+ $this->plugin = $this->objectManager->getObject(
+ InputParamsResolverPlugin::class,
+ [
+ 'request' => $this->request
+ ]
+ );
+ }
+
+ public function testAfterResolve()
+ {
+ $this->route->expects($this->once())
+ ->method('getServiceClass')
+ ->willReturn(ProductRepositoryInterface::class);
+ $this->route->expects($this->once())
+ ->method('getServiceMethod')
+ ->willReturn('save');
+ $this->product->expects($this->once())
+ ->method('setData')
+ ->with($this->saveRewritesHistory, true);
+
+ $this->plugin->afterResolve($this->subject, $this->result);
+ }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/composer.json b/app/code/Magento/CatalogUrlRewrite/composer.json
index e373d8c8c175..b4ceff96b50b 100644
--- a/app/code/Magento/CatalogUrlRewrite/composer.json
+++ b/app/code/Magento/CatalogUrlRewrite/composer.json
@@ -16,6 +16,9 @@
"magento/module-ui": "*",
"magento/module-url-rewrite": "*"
},
+ "suggest": {
+ "magento/module-webapi": "*"
+ },
"type": "magento2-module",
"license": [
"OSL-3.0",
diff --git a/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml
index ac8beb362f0f..9c5186a5ec0a 100644
--- a/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml
+++ b/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml
@@ -6,5 +6,7 @@
*/
-->
-
+
+
+
diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
index 55f4d6727337..2b95de24d020 100644
--- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
+++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
@@ -11,11 +11,13 @@
use Magento\Framework\Pricing\PriceCurrencyInterface;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Widget\Block\BlockInterface;
+use Magento\Framework\Url\EncoderInterface;
/**
* Catalog Products List widget block
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implements BlockInterface, IdentityInterface
{
@@ -94,6 +96,11 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem
*/
private $json;
+ /**
+ * @var \Magento\Framework\Url\EncoderInterface|null
+ */
+ private $urlEncoder;
+
/**
* @param \Magento\Catalog\Block\Product\Context $context
* @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
@@ -104,6 +111,7 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem
* @param \Magento\Widget\Helper\Conditions $conditionsHelper
* @param array $data
* @param Json|null $json
+ * @param \Magento\Framework\Url\EncoderInterface|null $urlEncoder
*/
public function __construct(
\Magento\Catalog\Block\Product\Context $context,
@@ -114,7 +122,8 @@ public function __construct(
\Magento\CatalogWidget\Model\Rule $rule,
\Magento\Widget\Helper\Conditions $conditionsHelper,
array $data = [],
- Json $json = null
+ Json $json = null,
+ EncoderInterface $urlEncoder = null
) {
$this->productCollectionFactory = $productCollectionFactory;
$this->catalogProductVisibility = $catalogProductVisibility;
@@ -123,6 +132,7 @@ public function __construct(
$this->rule = $rule;
$this->conditionsHelper = $conditionsHelper;
$this->json = $json ?: ObjectManager::getInstance()->get(Json::class);
+ $this->urlEncoder = $urlEncoder ?: ObjectManager::getInstance()->get(EncoderInterface::class);
parent::__construct(
$context,
$data
@@ -153,6 +163,7 @@ protected function _construct()
* Get key pieces for caching block content
*
* @return array
+ * @SuppressWarnings(PHPMD.RequestAwareBlockMethod)
*/
public function getCacheKeyInfo()
{
@@ -230,6 +241,7 @@ protected function _beforeToHtml()
* Prepare and return product collection
*
* @return \Magento\Catalog\Model\ResourceModel\Product\Collection
+ * @SuppressWarnings(PHPMD.RequestAwareBlockMethod)
*/
public function createCollection()
{
@@ -409,6 +421,22 @@ private function getPriceCurrency()
return $this->priceCurrency;
}
+ /**
+ * @inheritdoc
+ */
+ public function getAddToCartUrl($product, $additional = [])
+ {
+ $requestingPageUrl = $this->getRequest()->getParam('requesting_page_url');
+
+ if (!empty($requestingPageUrl)) {
+ $additional['useUencPlaceholder'] = true;
+ $url = parent::getAddToCartUrl($product, $additional);
+ return str_replace('%25uenc%25', $this->urlEncoder->encode($requestingPageUrl), $url);
+ }
+
+ return parent::getAddToCartUrl($product, $additional);
+ }
+
/**
* Get widget block name
*
diff --git a/app/code/Magento/CatalogWidget/Test/Mftf/Section/ProductListWidgetSection.xml b/app/code/Magento/CatalogWidget/Test/Mftf/Section/ProductListWidgetSection.xml
new file mode 100644
index 000000000000..03bef8ffa3b7
--- /dev/null
+++ b/app/code/Magento/CatalogWidget/Test/Mftf/Section/ProductListWidgetSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogWidget/Test/Mftf/Test/CatalogProductListWidgetOperatorsTest.xml b/app/code/Magento/CatalogWidget/Test/Mftf/Test/CatalogProductListWidgetOperatorsTest.xml
index 53a4ccb608a6..32bea8b604cf 100644
--- a/app/code/Magento/CatalogWidget/Test/Mftf/Test/CatalogProductListWidgetOperatorsTest.xml
+++ b/app/code/Magento/CatalogWidget/Test/Mftf/Test/CatalogProductListWidgetOperatorsTest.xml
@@ -17,6 +17,7 @@
+
diff --git a/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml b/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml
index f2273f7d44ff..6789ace243b8 100644
--- a/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml
+++ b/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml
@@ -61,7 +61,7 @@
isSaleable()): ?>
- getTypeInstance()->hasRequiredOptions($_item)): ?>
+ getTypeInstance()->isPossibleBuyFromList($_item)): ?>
= $block->escapeHtml(__('Add to Cart')) ?>
diff --git a/app/code/Magento/Checkout/CustomerData/Cart.php b/app/code/Magento/Checkout/CustomerData/Cart.php
index ddb077462ef1..01e91d75c02d 100644
--- a/app/code/Magento/Checkout/CustomerData/Cart.php
+++ b/app/code/Magento/Checkout/CustomerData/Cart.php
@@ -82,7 +82,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getSectionData()
{
@@ -158,11 +158,10 @@ protected function getRecentItems()
: $item->getProduct();
$products = $this->catalogUrl->getRewriteByProductStore([$product->getId() => $item->getStoreId()]);
- if (!isset($products[$product->getId()])) {
- continue;
+ if (isset($products[$product->getId()])) {
+ $urlDataObject = new \Magento\Framework\DataObject($products[$product->getId()]);
+ $item->getProduct()->setUrlDataObject($urlDataObject);
}
- $urlDataObject = new \Magento\Framework\DataObject($products[$product->getId()]);
- $item->getProduct()->setUrlDataObject($urlDataObject);
}
$items[] = $this->itemPoolInterface->getItemData($item);
}
diff --git a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php
index fd120f0a37a4..f30bd73deeae 100644
--- a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php
+++ b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php
@@ -282,10 +282,12 @@ public function getConfig()
$quote = $this->checkoutSession->getQuote();
$quoteId = $quote->getId();
$email = $quote->getShippingAddress()->getEmail();
+ $quoteItemData = $this->getQuoteItemData();
$output['formKey'] = $this->formKey->getFormKey();
$output['customerData'] = $this->getCustomerData();
$output['quoteData'] = $this->getQuoteData();
- $output['quoteItemData'] = $this->getQuoteItemData();
+ $output['quoteItemData'] = $quoteItemData;
+ $output['quoteMessages'] = $this->getQuoteItemsMessages($quoteItemData);
$output['isCustomerLoggedIn'] = $this->isCustomerLoggedIn();
$output['selectedShippingMethod'] = $this->getSelectedShippingMethod();
if ($email && !$this->isCustomerLoggedIn()) {
@@ -316,6 +318,7 @@ public function getConfig()
);
$output['postCodes'] = $this->postCodesConfig->getPostCodes();
$output['imageData'] = $this->imageProvider->getImages($quoteId);
+
$output['totalsData'] = $this->getTotalsData();
$output['shippingPolicy'] = [
'isEnabled' => $this->scopeConfig->isSetFlag(
@@ -450,6 +453,7 @@ private function getQuoteItemData()
$quoteItem->getProduct(),
'product_thumbnail_image'
)->getUrl();
+ $quoteItemData[$index]['message'] = $quoteItem->getMessage();
}
}
return $quoteItemData;
@@ -776,4 +780,22 @@ private function getAttributeLabels(array $customAttribute, string $customAttrib
return $attributeOptionLabels;
}
+
+ /**
+ * Get notification messages for the quote items
+ *
+ * @param array $quoteItemData
+ * @return array
+ */
+ private function getQuoteItemsMessages(array $quoteItemData): array
+ {
+ $quoteItemsMessages = [];
+ if ($quoteItemData) {
+ foreach ($quoteItemData as $item) {
+ $quoteItemsMessages[$item['item_id']] = $item['message'];
+ }
+ }
+
+ return $quoteItemsMessages;
+ }
}
diff --git a/app/code/Magento/Checkout/Observer/SalesQuoteSaveAfterObserver.php b/app/code/Magento/Checkout/Observer/SalesQuoteSaveAfterObserver.php
index d926e33d5411..6bc7965ff5e3 100644
--- a/app/code/Magento/Checkout/Observer/SalesQuoteSaveAfterObserver.php
+++ b/app/code/Magento/Checkout/Observer/SalesQuoteSaveAfterObserver.php
@@ -7,6 +7,9 @@
use Magento\Framework\Event\ObserverInterface;
+/**
+ * Class SalesQuoteSaveAfterObserver
+ */
class SalesQuoteSaveAfterObserver implements ObserverInterface
{
/**
@@ -24,15 +27,18 @@ public function __construct(\Magento\Checkout\Model\Session $checkoutSession)
}
/**
+ * Assign quote to session
+ *
* @param \Magento\Framework\Event\Observer $observer
* @return void
*/
public function execute(\Magento\Framework\Event\Observer $observer)
{
+ /* @var \Magento\Quote\Model\Quote $quote */
$quote = $observer->getEvent()->getQuote();
- /* @var $quote \Magento\Quote\Model\Quote */
+
if ($quote->getIsCheckoutCart()) {
- $this->checkoutSession->getQuoteId($quote->getId());
+ $this->checkoutSession->setQuoteId($quote->getId());
}
}
}
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml
index 18bf70a613b5..43aa8bfa9bdf 100644
--- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml
@@ -21,11 +21,22 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -36,7 +47,7 @@
-
+
@@ -131,6 +142,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -161,7 +190,7 @@
-
+
@@ -236,6 +265,7 @@
+
@@ -243,4 +273,10 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
index 0499a57a9211..72c5648991ef 100644
--- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
@@ -90,7 +90,7 @@
-
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml
index 26bc6ff641a9..dc82932ec5ca 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml
@@ -13,4 +13,26 @@
- Bahamas
+
+
+ - Australia
+ - Brazil
+ - Canada
+ - Croatia
+ - Estonia
+ - India
+ - Latvia
+ - Lithuania
+ - Romania
+ - Spain
+ - Switzerland
+ - United States
+ - Australia
+
+
+
+
+ - United Kingdom
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml
index 70bb0532222a..3dcf399c9ad5 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml
@@ -13,6 +13,7 @@
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CheckNotVisibleProductInMinicartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CheckNotVisibleProductInMinicartTest.xml
new file mode 100644
index 000000000000..4b4ca1935fd7
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/CheckNotVisibleProductInMinicartTest.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml
index 00d80cc2a94d..35e0058440f6 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml
@@ -158,13 +158,13 @@
-
+
-
+
-
+
-
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml
index 05a6939941f3..371826c9e784 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml
@@ -158,13 +158,13 @@
-
+
-
+
-
+
-
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml
index 1ef7403e94ce..3adaf83ff101 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml
@@ -26,10 +26,13 @@
-
+
+
+
+
+
-
@@ -76,6 +79,7 @@
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithoutRegionTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithoutRegionTest.xml
new file mode 100644
index 000000000000..0cc0dcf38e31
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithoutRegionTest.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml
index f9533fd946f3..9c7aa064e4f0 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml
@@ -69,6 +69,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml
index ace50aa3e6d3..2cc21df85ab6 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml
@@ -36,6 +36,7 @@
+
diff --git a/app/code/Magento/Checkout/Test/Unit/Observer/SalesQuoteSaveAfterObserverTest.php b/app/code/Magento/Checkout/Test/Unit/Observer/SalesQuoteSaveAfterObserverTest.php
index 6070bb5d424c..dabaf173d90b 100644
--- a/app/code/Magento/Checkout/Test/Unit/Observer/SalesQuoteSaveAfterObserverTest.php
+++ b/app/code/Magento/Checkout/Test/Unit/Observer/SalesQuoteSaveAfterObserverTest.php
@@ -30,13 +30,14 @@ protected function setUp()
public function testSalesQuoteSaveAfter()
{
+ $quoteId = 7;
$observer = $this->createMock(\Magento\Framework\Event\Observer::class);
$observer->expects($this->once())->method('getEvent')->will(
$this->returnValue(new \Magento\Framework\DataObject(
- ['quote' => new \Magento\Framework\DataObject(['is_checkout_cart' => 1, 'id' => 7])]
+ ['quote' => new \Magento\Framework\DataObject(['is_checkout_cart' => 1, 'id' => $quoteId])]
))
);
- $this->checkoutSession->expects($this->once())->method('getQuoteId')->with(7);
+ $this->checkoutSession->expects($this->once())->method('setQuoteId')->with($quoteId);
$this->object->execute($observer);
}
diff --git a/app/code/Magento/Checkout/view/adminhtml/email/failed_payment.html b/app/code/Magento/Checkout/view/adminhtml/email/failed_payment.html
index fb55f9b601dc..03ad7d9e8d84 100644
--- a/app/code/Magento/Checkout/view/adminhtml/email/failed_payment.html
+++ b/app/code/Magento/Checkout/view/adminhtml/email/failed_payment.html
@@ -23,43 +23,43 @@ {{trans "Payment Transaction Failed"}}
- {{trans "Reason"}}
+ {{trans "Reason"}}
{{var reason}}
- {{trans "Checkout Type"}}
+ {{trans "Checkout Type"}}
{{var checkoutType}}
- {{trans "Customer:"}}
+ {{trans "Customer:"}}
{{var customer}} <{{var customerEmail}}>
- {{trans "Items"}}
+ {{trans "Items"}}
{{var items|raw}}
- {{trans "Total:"}}
+ {{trans "Total:"}}
{{var total}}
- {{trans "Billing Address:"}}
+ {{trans "Billing Address:"}}
{{var billingAddress.format('html')|raw}}
- {{trans "Shipping Address:"}}
+ {{trans "Shipping Address:"}}
{{var shippingAddress.format('html')|raw}}
- {{trans "Shipping Method:"}}
+ {{trans "Shipping Method:"}}
{{var shippingMethod}}
- {{trans "Payment Method:"}}
+ {{trans "Payment Method:"}}
{{var paymentMethod}}
- {{trans "Date & Time:"}}
+ {{trans "Date & Time:"}}
{{var dateAndTime}}
diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml
index d4fadedf5d7a..64b70e80bd84 100644
--- a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml
+++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml
@@ -404,6 +404,10 @@
- Magento_Checkout/js/view/summary/item/details/subtotal
- after_details
+ -
+
- Magento_Checkout/js/view/summary/item/details/message
+ - item_message
+
diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml
index 1876cf2edb78..da0a83f05ef6 100644
--- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml
+++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml
@@ -12,8 +12,6 @@
-
- = /* @escapeNotVerified */ __('Edit') ?>
-
-
+ = /* @escapeNotVerified */ __('Edit') ?>
+
diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml
index e6d0260cf230..20be9cd010c6 100644
--- a/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml
+++ b/app/code/Magento/Checkout/view/frontend/templates/cart/minicart.phtml
@@ -41,6 +41,14 @@
= $block->getChildHtml('minicart.addons') ?>
+
+
@@ -59,13 +74,11 @@ endif; ?>
onchange="order.selectAddress(this, '= /* @escapeNotVerified */ $_fieldsContainerId ?>')"
class="admin__control-select">
= /* @escapeNotVerified */ __('Add New Address') ?>
- getAddressCollection() as $_address): ?>
- getAddressAsString($_address)!=$block->getAddressAsString($block->getAddress())): ?>
+ $address): ?>
getId() == $block->getAddressId()): ?> selected="selected">
- = /* @escapeNotVerified */ $block->getAddressAsString($_address) ?>
+ value="= /* @escapeNotVerified */ $addressId ?>"getAddressId()): ?> selected="selected">
+ = /* @escapeNotVerified */ $block->escapeHtml($customerAddressFormatter->getAddressAsString($address)) ?>
-
diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/shipping/method/form.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/shipping/method/form.phtml
index 9e0394f6430b..65d3a612e513 100644
--- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/shipping/method/form.phtml
+++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/shipping/method/form.phtml
@@ -100,12 +100,8 @@ require(['prototype'], function(){
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 259ca2165e64..c508a5ecdfa5 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
@@ -4,14 +4,17 @@
*/
define([
- "jquery",
+ 'jquery',
'Magento_Ui/js/modal/confirm',
'Magento_Ui/js/modal/alert',
- "mage/translate",
- "prototype",
- "Magento_Catalog/catalog/product/composite/configure",
+ 'mage/template',
+ 'text!Magento_Sales/templates/order/create/shipping/reload.html',
+ 'text!Magento_Sales/templates/order/create/payment/reload.html',
+ 'mage/translate',
+ 'prototype',
+ 'Magento_Catalog/catalog/product/composite/configure',
'Magento_Ui/js/lib/view/utils/async'
-], function(jQuery, confirm, alert){
+], function (jQuery, confirm, alert, template, shippingTemplate, paymentTemplate) {
window.AdminOrder = new Class.create();
@@ -29,7 +32,7 @@ define([
this.gridProducts = $H({});
this.gridProductsGift = $H({});
this.billingAddressContainer = '';
- this.shippingAddressContainer= '';
+ this.shippingAddressContainer = '';
this.isShippingMethodReseted = data.shipping_method_reseted ? data.shipping_method_reseted : false;
this.overlayData = $H({});
this.giftMessageDataChanged = false;
@@ -39,7 +42,19 @@ define([
this.isOnlyVirtualProduct = false;
this.excludedPaymentMethods = [];
this.summarizePrice = true;
- this.timerId = null;
+ this.shippingTemplate = template(shippingTemplate, {
+ data: {
+ title: jQuery.mage.__('Shipping Method'),
+ linkText: jQuery.mage.__('Get shipping methods and rates')
+ }
+ });
+ this.paymentTemplate = template(paymentTemplate, {
+ data: {
+ title: jQuery.mage.__('Payment Method'),
+ linkText: jQuery.mage.__('Get available payment methods')
+ }
+ });
+
jQuery.async('#order-items', (function(){
this.dataArea = new OrderFormArea('data', $(this.getAreaId('data')), this);
this.itemsArea = Object.extend(new OrderFormArea('items', $(this.getAreaId('items')), this), {
@@ -168,43 +183,51 @@ define([
var data = this.serializeData(container);
data[el.name] = id;
- if(this.isShippingField(container) && !this.isShippingMethodReseted){
+
+ this.resetPaymentMethod();
+ if (this.isShippingField(container) && !this.isShippingMethodReseted) {
this.resetShippingMethod(data);
- }
- else{
+ } else{
this.saveData(data);
}
},
- isShippingField : function(fieldId){
- if(this.shippingAsBilling){
+ /**
+ * Checks if the field belongs to the shipping address.
+ *
+ * @param {String} fieldId
+ * @return {Boolean}
+ */
+ isShippingField: function (fieldId) {
+ if (this.shippingAsBilling) {
return fieldId.include('billing');
}
+
return fieldId.include('shipping');
},
- isBillingField : function(fieldId){
+ /**
+ * Checks if the field belongs to the billing address.
+ *
+ * @param {String} fieldId
+ * @return {Boolean}
+ */
+ isBillingField: function (fieldId) {
return fieldId.include('billing');
},
- bindAddressFields : function(container) {
- var fields = $(container).select('input', 'select', 'textarea');
- for(var i=0;i
+
+
+ <%- data.title %>
+
+
diff --git a/app/code/Magento/Sales/view/adminhtml/web/templates/order/create/shipping/reload.html b/app/code/Magento/Sales/view/adminhtml/web/templates/order/create/shipping/reload.html
new file mode 100644
index 000000000000..6b191ee81a45
--- /dev/null
+++ b/app/code/Magento/Sales/view/adminhtml/web/templates/order/create/shipping/reload.html
@@ -0,0 +1,19 @@
+
+
+
+ <%- data.title %>
+
+
diff --git a/app/code/Magento/Sales/view/frontend/requirejs-config.js b/app/code/Magento/Sales/view/frontend/requirejs-config.js
index 658960c749f8..4d323684afff 100644
--- a/app/code/Magento/Sales/view/frontend/requirejs-config.js
+++ b/app/code/Magento/Sales/view/frontend/requirejs-config.js
@@ -7,7 +7,9 @@ var config = {
map: {
'*': {
giftMessage: 'Magento_Sales/js/gift-message',
- ordersReturns: 'Magento_Sales/js/orders-returns'
+ ordersReturns: 'Magento_Sales/js/orders-returns',
+ 'Magento_Sales/gift-message': 'Magento_Sales/js/gift-message',
+ 'Magento_Sales/orders-returns': 'Magento_Sales/js/orders-returns'
}
}
};
diff --git a/app/code/Magento/Sales/view/frontend/templates/order/items.phtml b/app/code/Magento/Sales/view/frontend/templates/order/items.phtml
index e43d32760feb..dc179b6ee4ac 100644
--- a/app/code/Magento/Sales/view/frontend/templates/order/items.phtml
+++ b/app/code/Magento/Sales/view/frontend/templates/order/items.phtml
@@ -29,9 +29,10 @@
getItems(); ?>
+
getParentItem()) continue; ?>
-
+
= $block->getItemHtml($item) ?>
helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $item) && $item->getGiftMessageId()): ?>
helper('Magento\GiftMessage\Helper\Message')->getGiftMessageForEntity($item); ?>
@@ -62,8 +63,8 @@
-
+
isPagerDisplayed()): ?>
diff --git a/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml b/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml
index 5ecf1ebe893b..9b3633fde60b 100644
--- a/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml
+++ b/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml
@@ -26,14 +26,18 @@
@_background
diff --git a/lib/web/css/docs/source/_utilities.less b/lib/web/css/docs/source/_utilities.less
index 312eeffe488c..1ce15a9a50e2 100644
--- a/lib/web/css/docs/source/_utilities.less
+++ b/lib/web/css/docs/source/_utilities.less
@@ -367,66 +367,3 @@
//
//
//
-
-// # .lib-url-check()
-//
-// The .lib-url-check()
mixin wraps passed value with 'url( ... )' and returns @lib-url-check-output
variable. Can be used with .lib-css()
mixin.
-//
-
-.example-url-check {
- // Set image path variable
- @_icon-image: '/images/test.png';
-
- // "Call" the mixin
- .lib-url-check(@_icon-image);
-
- // Will return url('/images/test.png')
- .lib-css(background, #eee @lib-url-check-output no-repeat 0 0);
-}
-
-//
-// If the variable is set to false
, the .lib-url-check()
will return false.
-//
-// ```
-//
-// Block with background.
-//
-// ```
-//
-
-.example-url-check-false {
- // Set usage image path to false
- @_icon-image: false;
-
- // "Call" the mixin
- .lib-url-check(@_icon-image);
-
- // Will return 'false' and outputs nothing
- .lib-css(background, #eee @lib-url-check-output no-repeat 0 0);
-}
-
-// ```
-//
-// Block with no background.
-//
-// ```
-//
-
-// # .lib-url-check() variables
-//
-//
-//
-//
-// Mixin variable
-// Allowed values
-// Output variable
-// Comment
-//
-//
-// @_path
-// '' | false | value
-// @lib-url-check-output
-// Passed url to wrap in 'url( ... )'. If the 'false' value passed mixin will return 'false'
-//
-//
-//
diff --git a/lib/web/css/docs/variables.html b/lib/web/css/docs/variables.html
index 4f353dc1554a..ebbf2122ab20 100644
--- a/lib/web/css/docs/variables.html
+++ b/lib/web/css/docs/variables.html
@@ -3507,7 +3507,7 @@